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 <stdlib.h>
18 #include <time.h>
19
20 #include <fstream>
21 #include <unordered_map>
22
23 #include <gtest/gtest.h>
24 #include <rapidjson/document.h>
25 #include <rapidjson/filewritestream.h>
26 #include <rapidjson/istreamwrapper.h>
27 #include <rapidjson/prettywriter.h>
28
29 // We directly call into a function to register the parameterized tests. This saves spinning up
30 // a subprocess with a new gtest filter.
31 #include <gtest/../../src/gtest-internal-inl.h>
32
33 namespace js = rapidjson;
34
35 namespace angle
36 {
37 namespace
38 {
39 constexpr char kBatchId[] = "--batch-id=";
40 constexpr char kFilterFileArg[] = "--filter-file=";
41 constexpr char kFlakyRetries[] = "--flaky-retries=";
42 constexpr char kGTestListTests[] = "--gtest_list_tests";
43 constexpr char kHistogramJsonFileArg[] = "--histogram-json-file=";
44 constexpr char kListTests[] = "--list-tests";
45 constexpr char kPrintTestStdout[] = "--print-test-stdout";
46 constexpr char kResultFileArg[] = "--results-file=";
47 constexpr char kTestTimeoutArg[] = "--test-timeout=";
48 constexpr char kDisableCrashHandler[] = "--disable-crash-handler";
49
50 constexpr char kStartedTestString[] = "[ RUN ] ";
51 constexpr char kPassedTestString[] = "[ OK ] ";
52 constexpr char kFailedTestString[] = "[ FAILED ] ";
53
54 #if defined(NDEBUG)
55 constexpr int kDefaultTestTimeout = 20;
56 #else
57 constexpr int kDefaultTestTimeout = 60;
58 #endif
59 #if defined(NDEBUG)
60 constexpr int kDefaultBatchTimeout = 240;
61 #else
62 constexpr int kDefaultBatchTimeout = 600;
63 #endif
64 constexpr int kDefaultBatchSize = 256;
65 constexpr double kIdleMessageTimeout = 15.0;
66 constexpr int kDefaultMaxProcesses = 16;
67
ParseFlagValue(const char * flag,const char * argument)68 const char *ParseFlagValue(const char *flag, const char *argument)
69 {
70 if (strstr(argument, flag) == argument)
71 {
72 return argument + strlen(flag);
73 }
74
75 return nullptr;
76 }
77
ParseIntArg(const char * flag,const char * argument,int * valueOut)78 bool ParseIntArg(const char *flag, const char *argument, int *valueOut)
79 {
80 const char *value = ParseFlagValue(flag, argument);
81 if (!value)
82 {
83 return false;
84 }
85
86 char *end = nullptr;
87 const long longValue = strtol(value, &end, 10);
88
89 if (*end != '\0')
90 {
91 printf("Error parsing integer flag value.\n");
92 exit(EXIT_FAILURE);
93 }
94
95 if (longValue == LONG_MAX || longValue == LONG_MIN || static_cast<int>(longValue) != longValue)
96 {
97 printf("Overflow when parsing integer flag value.\n");
98 exit(EXIT_FAILURE);
99 }
100
101 *valueOut = static_cast<int>(longValue);
102 return true;
103 }
104
ParseIntArgNoDelete(const char * flag,const char * argument,int * valueOut)105 bool ParseIntArgNoDelete(const char *flag, const char *argument, int *valueOut)
106 {
107 ParseIntArg(flag, argument, valueOut);
108 return false;
109 }
110
ParseFlag(const char * expected,const char * actual,bool * flagOut)111 bool ParseFlag(const char *expected, const char *actual, bool *flagOut)
112 {
113 if (strcmp(expected, actual) == 0)
114 {
115 *flagOut = true;
116 return true;
117 }
118 return false;
119 }
120
ParseStringArg(const char * flag,const char * argument,std::string * valueOut)121 bool ParseStringArg(const char *flag, const char *argument, std::string *valueOut)
122 {
123 const char *value = ParseFlagValue(flag, argument);
124 if (!value)
125 {
126 return false;
127 }
128
129 *valueOut = value;
130 return true;
131 }
132
DeleteArg(int * argc,char ** argv,int argIndex)133 void DeleteArg(int *argc, char **argv, int argIndex)
134 {
135 // Shift the remainder of the argv list left by one. Note that argv has (*argc + 1) elements,
136 // the last one always being NULL. The following loop moves the trailing NULL element as well.
137 for (int index = argIndex; index < *argc; ++index)
138 {
139 argv[index] = argv[index + 1];
140 }
141 (*argc)--;
142 }
143
AddArg(int * argc,char ** argv,const char * arg)144 void AddArg(int *argc, char **argv, const char *arg)
145 {
146 // This unsafe const_cast is necessary to work around gtest limitations.
147 argv[*argc] = const_cast<char *>(arg);
148 argv[*argc + 1] = nullptr;
149 (*argc)++;
150 }
151
ResultTypeToString(TestResultType type)152 const char *ResultTypeToString(TestResultType type)
153 {
154 switch (type)
155 {
156 case TestResultType::Crash:
157 return "CRASH";
158 case TestResultType::Fail:
159 return "FAIL";
160 case TestResultType::Pass:
161 return "PASS";
162 case TestResultType::NoResult:
163 return "SKIP";
164 case TestResultType::Timeout:
165 return "TIMEOUT";
166 case TestResultType::Unknown:
167 return "UNKNOWN";
168 }
169 }
170
GetResultTypeFromString(const std::string & str)171 TestResultType GetResultTypeFromString(const std::string &str)
172 {
173 if (str == "CRASH")
174 return TestResultType::Crash;
175 if (str == "FAIL")
176 return TestResultType::Fail;
177 if (str == "PASS")
178 return TestResultType::Pass;
179 if (str == "SKIP")
180 return TestResultType::NoResult;
181 if (str == "TIMEOUT")
182 return TestResultType::Timeout;
183 return TestResultType::Unknown;
184 }
185
ResultTypeToJSString(TestResultType type,js::Document::AllocatorType * allocator)186 js::Value ResultTypeToJSString(TestResultType type, js::Document::AllocatorType *allocator)
187 {
188 js::Value jsName;
189 jsName.SetString(ResultTypeToString(type), *allocator);
190 return jsName;
191 }
192
WriteJsonFile(const std::string & outputFile,js::Document * doc)193 bool WriteJsonFile(const std::string &outputFile, js::Document *doc)
194 {
195 FILE *fp = fopen(outputFile.c_str(), "w");
196 if (!fp)
197 {
198 return false;
199 }
200
201 constexpr size_t kBufferSize = 0xFFFF;
202 std::vector<char> writeBuffer(kBufferSize);
203 js::FileWriteStream os(fp, writeBuffer.data(), kBufferSize);
204 js::PrettyWriter<js::FileWriteStream> writer(os);
205 if (!doc->Accept(writer))
206 {
207 fclose(fp);
208 return false;
209 }
210 fclose(fp);
211 return true;
212 }
213
214 // Writes out a TestResults to the Chromium JSON Test Results format.
215 // https://chromium.googlesource.com/chromium/src.git/+/master/docs/testing/json_test_results_format.md
WriteResultsFile(bool interrupted,const TestResults & testResults,const std::string & outputFile,const char * testSuiteName)216 void WriteResultsFile(bool interrupted,
217 const TestResults &testResults,
218 const std::string &outputFile,
219 const char *testSuiteName)
220 {
221 time_t ltime;
222 time(<ime);
223 struct tm *timeinfo = gmtime(<ime);
224 ltime = mktime(timeinfo);
225
226 uint64_t secondsSinceEpoch = static_cast<uint64_t>(ltime);
227
228 js::Document doc;
229 doc.SetObject();
230
231 js::Document::AllocatorType &allocator = doc.GetAllocator();
232
233 doc.AddMember("interrupted", interrupted, allocator);
234 doc.AddMember("path_delimiter", ".", allocator);
235 doc.AddMember("version", 3, allocator);
236 doc.AddMember("seconds_since_epoch", secondsSinceEpoch, allocator);
237
238 js::Value tests;
239 tests.SetObject();
240
241 std::map<TestResultType, uint32_t> counts;
242
243 for (const auto &resultIter : testResults.results)
244 {
245 const TestIdentifier &id = resultIter.first;
246 const TestResult &result = resultIter.second;
247
248 js::Value jsResult;
249 jsResult.SetObject();
250
251 counts[result.type]++;
252
253 std::string actualResult;
254 for (uint32_t fail = 0; fail < result.flakyFailures; ++fail)
255 {
256 actualResult += "FAIL ";
257 }
258
259 actualResult += ResultTypeToString(result.type);
260
261 std::string expectedResult;
262 if (result.flakyFailures > 0)
263 {
264 expectedResult = "FAIL PASS";
265 jsResult.AddMember("is_flaky", true, allocator);
266 }
267 else
268 {
269 expectedResult = "PASS";
270 }
271
272 jsResult.AddMember("actual", actualResult, allocator);
273 jsResult.AddMember("expected", expectedResult, allocator);
274
275 if (result.type != TestResultType::Pass)
276 {
277 jsResult.AddMember("is_unexpected", true, allocator);
278 }
279
280 js::Value times;
281 times.SetArray();
282 times.PushBack(result.elapsedTimeSeconds, allocator);
283
284 jsResult.AddMember("times", times, allocator);
285
286 char testName[500];
287 id.sprintfName(testName);
288 js::Value jsName;
289 jsName.SetString(testName, allocator);
290
291 tests.AddMember(jsName, jsResult, allocator);
292 }
293
294 js::Value numFailuresByType;
295 numFailuresByType.SetObject();
296
297 for (const auto &countIter : counts)
298 {
299 TestResultType type = countIter.first;
300 uint32_t count = countIter.second;
301
302 js::Value jsCount(count);
303 numFailuresByType.AddMember(ResultTypeToJSString(type, &allocator), jsCount, allocator);
304 }
305
306 doc.AddMember("num_failures_by_type", numFailuresByType, allocator);
307
308 doc.AddMember("tests", tests, allocator);
309
310 printf("Writing test results to %s\n", outputFile.c_str());
311
312 if (!WriteJsonFile(outputFile, &doc))
313 {
314 printf("Error writing test results file.\n");
315 }
316 }
317
WriteHistogramJson(const HistogramWriter & histogramWriter,const std::string & outputFile,const char * testSuiteName)318 void WriteHistogramJson(const HistogramWriter &histogramWriter,
319 const std::string &outputFile,
320 const char *testSuiteName)
321 {
322 js::Document doc;
323 doc.SetArray();
324
325 histogramWriter.getAsJSON(&doc);
326
327 printf("Writing histogram json to %s\n", outputFile.c_str());
328
329 if (!WriteJsonFile(outputFile, &doc))
330 {
331 printf("Error writing histogram json file.\n");
332 }
333 }
334
WriteOutputFiles(bool interrupted,const TestResults & testResults,const std::string & resultsFile,const HistogramWriter & histogramWriter,const std::string & histogramJsonOutputFile,const char * testSuiteName)335 void WriteOutputFiles(bool interrupted,
336 const TestResults &testResults,
337 const std::string &resultsFile,
338 const HistogramWriter &histogramWriter,
339 const std::string &histogramJsonOutputFile,
340 const char *testSuiteName)
341 {
342 if (!resultsFile.empty())
343 {
344 WriteResultsFile(interrupted, testResults, resultsFile, testSuiteName);
345 }
346
347 if (!histogramJsonOutputFile.empty())
348 {
349 WriteHistogramJson(histogramWriter, histogramJsonOutputFile, testSuiteName);
350 }
351 }
352
UpdateCurrentTestResult(const testing::TestResult & resultIn,TestResults * resultsOut)353 void UpdateCurrentTestResult(const testing::TestResult &resultIn, TestResults *resultsOut)
354 {
355 TestResult &resultOut = resultsOut->results[resultsOut->currentTest];
356
357 // Note: Crashes and Timeouts are detected by the crash handler and a watchdog thread.
358 if (resultIn.Skipped())
359 {
360 resultOut.type = TestResultType::NoResult;
361 }
362 else if (resultIn.Failed())
363 {
364 resultOut.type = TestResultType::Fail;
365 }
366 else
367 {
368 resultOut.type = TestResultType::Pass;
369 }
370
371 resultOut.elapsedTimeSeconds = resultsOut->currentTestTimer.getElapsedTime();
372 }
373
GetTestIdentifier(const testing::TestInfo & testInfo)374 TestIdentifier GetTestIdentifier(const testing::TestInfo &testInfo)
375 {
376 return {testInfo.test_suite_name(), testInfo.name()};
377 }
378
379 class TestEventListener : public testing::EmptyTestEventListener
380 {
381 public:
382 // Note: TestResults is owned by the TestSuite. It should outlive TestEventListener.
TestEventListener(const std::string & resultsFile,const std::string & histogramJsonFile,const char * testSuiteName,TestResults * testResults,HistogramWriter * histogramWriter)383 TestEventListener(const std::string &resultsFile,
384 const std::string &histogramJsonFile,
385 const char *testSuiteName,
386 TestResults *testResults,
387 HistogramWriter *histogramWriter)
388 : mResultsFile(resultsFile),
389 mHistogramJsonFile(histogramJsonFile),
390 mTestSuiteName(testSuiteName),
391 mTestResults(testResults),
392 mHistogramWriter(histogramWriter)
393 {}
394
OnTestStart(const testing::TestInfo & testInfo)395 void OnTestStart(const testing::TestInfo &testInfo) override
396 {
397 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
398 mTestResults->currentTest = GetTestIdentifier(testInfo);
399 mTestResults->currentTestTimer.start();
400 }
401
OnTestEnd(const testing::TestInfo & testInfo)402 void OnTestEnd(const testing::TestInfo &testInfo) override
403 {
404 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
405 mTestResults->currentTestTimer.stop();
406 const testing::TestResult &resultIn = *testInfo.result();
407 UpdateCurrentTestResult(resultIn, mTestResults);
408 mTestResults->currentTest = TestIdentifier();
409 }
410
OnTestProgramEnd(const testing::UnitTest & testProgramInfo)411 void OnTestProgramEnd(const testing::UnitTest &testProgramInfo) override
412 {
413 std::lock_guard<std::mutex> guard(mTestResults->currentTestMutex);
414 mTestResults->allDone = true;
415 WriteOutputFiles(false, *mTestResults, mResultsFile, *mHistogramWriter, mHistogramJsonFile,
416 mTestSuiteName);
417 }
418
419 private:
420 std::string mResultsFile;
421 std::string mHistogramJsonFile;
422 const char *mTestSuiteName;
423 TestResults *mTestResults;
424 HistogramWriter *mHistogramWriter;
425 };
426
IsTestDisabled(const testing::TestInfo & testInfo)427 bool IsTestDisabled(const testing::TestInfo &testInfo)
428 {
429 return ::strstr(testInfo.name(), "DISABLED_") == testInfo.name();
430 }
431
432 using TestIdentifierFilter = std::function<bool(const TestIdentifier &id)>;
433
FilterTests(std::map<TestIdentifier,FileLine> * fileLinesOut,TestIdentifierFilter filter,bool alsoRunDisabledTests)434 std::vector<TestIdentifier> FilterTests(std::map<TestIdentifier, FileLine> *fileLinesOut,
435 TestIdentifierFilter filter,
436 bool alsoRunDisabledTests)
437 {
438 std::vector<TestIdentifier> tests;
439
440 const testing::UnitTest &testProgramInfo = *testing::UnitTest::GetInstance();
441 for (int suiteIndex = 0; suiteIndex < testProgramInfo.total_test_suite_count(); ++suiteIndex)
442 {
443 const testing::TestSuite &testSuite = *testProgramInfo.GetTestSuite(suiteIndex);
444 for (int testIndex = 0; testIndex < testSuite.total_test_count(); ++testIndex)
445 {
446 const testing::TestInfo &testInfo = *testSuite.GetTestInfo(testIndex);
447 TestIdentifier id = GetTestIdentifier(testInfo);
448 if (filter(id) && (!IsTestDisabled(testInfo) || alsoRunDisabledTests))
449 {
450 tests.emplace_back(id);
451
452 if (fileLinesOut)
453 {
454 (*fileLinesOut)[id] = {testInfo.file(), testInfo.line()};
455 }
456 }
457 }
458 }
459
460 return tests;
461 }
462
GetFilteredTests(std::map<TestIdentifier,FileLine> * fileLinesOut,bool alsoRunDisabledTests)463 std::vector<TestIdentifier> GetFilteredTests(std::map<TestIdentifier, FileLine> *fileLinesOut,
464 bool alsoRunDisabledTests)
465 {
466 TestIdentifierFilter gtestIDFilter = [](const TestIdentifier &id) {
467 return testing::internal::UnitTestOptions::FilterMatchesTest(id.testSuiteName, id.testName);
468 };
469
470 return FilterTests(fileLinesOut, gtestIDFilter, alsoRunDisabledTests);
471 }
472
GetShardTests(const std::vector<TestIdentifier> & allTests,int shardIndex,int shardCount,std::map<TestIdentifier,FileLine> * fileLinesOut,bool alsoRunDisabledTests)473 std::vector<TestIdentifier> GetShardTests(const std::vector<TestIdentifier> &allTests,
474 int shardIndex,
475 int shardCount,
476 std::map<TestIdentifier, FileLine> *fileLinesOut,
477 bool alsoRunDisabledTests)
478 {
479 std::vector<TestIdentifier> shardTests;
480
481 for (int testIndex = shardIndex; testIndex < static_cast<int>(allTests.size());
482 testIndex += shardCount)
483 {
484 shardTests.emplace_back(allTests[testIndex]);
485 }
486
487 return shardTests;
488 }
489
GetTestFilter(const std::vector<TestIdentifier> & tests)490 std::string GetTestFilter(const std::vector<TestIdentifier> &tests)
491 {
492 std::stringstream filterStream;
493
494 filterStream << "--gtest_filter=";
495
496 for (size_t testIndex = 0; testIndex < tests.size(); ++testIndex)
497 {
498 if (testIndex != 0)
499 {
500 filterStream << ":";
501 }
502
503 filterStream << tests[testIndex];
504 }
505
506 return filterStream.str();
507 }
508
ParseTestSuiteName(const char * executable)509 std::string ParseTestSuiteName(const char *executable)
510 {
511 const char *baseNameStart = strrchr(executable, GetPathSeparator());
512 if (!baseNameStart)
513 {
514 baseNameStart = executable;
515 }
516 else
517 {
518 baseNameStart++;
519 }
520
521 const char *suffix = GetExecutableExtension();
522 size_t suffixLen = strlen(suffix);
523 if (suffixLen == 0)
524 {
525 return baseNameStart;
526 }
527
528 if (!EndsWith(baseNameStart, suffix))
529 {
530 return baseNameStart;
531 }
532
533 return std::string(baseNameStart, baseNameStart + strlen(baseNameStart) - suffixLen);
534 }
535
GetTestResultsFromJSON(const js::Document & document,TestResults * resultsOut)536 bool GetTestResultsFromJSON(const js::Document &document, TestResults *resultsOut)
537 {
538 if (!document.HasMember("tests") || !document["tests"].IsObject())
539 {
540 return false;
541 }
542
543 const js::Value::ConstObject &tests = document["tests"].GetObject();
544 for (auto iter = tests.MemberBegin(); iter != tests.MemberEnd(); ++iter)
545 {
546 // Get test identifier.
547 const js::Value &name = iter->name;
548 if (!name.IsString())
549 {
550 return false;
551 }
552
553 TestIdentifier id;
554 if (!TestIdentifier::ParseFromString(name.GetString(), &id))
555 {
556 return false;
557 }
558
559 // Get test result.
560 const js::Value &value = iter->value;
561 if (!value.IsObject())
562 {
563 return false;
564 }
565
566 const js::Value::ConstObject &obj = value.GetObject();
567 if (!obj.HasMember("expected") || !obj.HasMember("actual"))
568 {
569 return false;
570 }
571
572 const js::Value &expected = obj["expected"];
573 const js::Value &actual = obj["actual"];
574
575 if (!expected.IsString() || !actual.IsString())
576 {
577 return false;
578 }
579
580 const std::string actualStr = actual.GetString();
581
582 TestResultType resultType = TestResultType::Unknown;
583 int flakyFailures = 0;
584 if (actualStr.find(' '))
585 {
586 std::istringstream strstr(actualStr);
587 std::string token;
588 while (std::getline(strstr, token, ' '))
589 {
590 resultType = GetResultTypeFromString(token);
591 if (resultType == TestResultType::Unknown)
592 {
593 printf("Failed to parse result type.\n");
594 return false;
595 }
596 if (resultType != TestResultType::Pass)
597 {
598 flakyFailures++;
599 }
600 }
601 }
602 else
603 {
604 resultType = GetResultTypeFromString(actualStr);
605 if (resultType == TestResultType::Unknown)
606 {
607 printf("Failed to parse result type.\n");
608 return false;
609 }
610 }
611
612 double elapsedTimeSeconds = 0.0;
613 if (obj.HasMember("times"))
614 {
615 const js::Value × = obj["times"];
616 if (!times.IsArray())
617 {
618 return false;
619 }
620
621 const js::Value::ConstArray ×Array = times.GetArray();
622 if (timesArray.Size() != 1 || !timesArray[0].IsDouble())
623 {
624 return false;
625 }
626
627 elapsedTimeSeconds = timesArray[0].GetDouble();
628 }
629
630 TestResult &result = resultsOut->results[id];
631 result.elapsedTimeSeconds = elapsedTimeSeconds;
632 result.type = resultType;
633 result.flakyFailures = flakyFailures;
634 }
635
636 return true;
637 }
638
MergeTestResults(TestResults * input,TestResults * output,int flakyRetries)639 bool MergeTestResults(TestResults *input, TestResults *output, int flakyRetries)
640 {
641 for (auto &resultsIter : input->results)
642 {
643 const TestIdentifier &id = resultsIter.first;
644 TestResult &inputResult = resultsIter.second;
645 TestResult &outputResult = output->results[id];
646
647 if (inputResult.type != TestResultType::NoResult)
648 {
649 if (outputResult.type != TestResultType::NoResult)
650 {
651 printf("Warning: duplicate entry for %s.%s.\n", id.testSuiteName.c_str(),
652 id.testName.c_str());
653 return false;
654 }
655
656 // Mark the tests that haven't exhausted their retries as 'SKIP'. This makes ANGLE
657 // attempt the test again.
658 uint32_t runCount = outputResult.flakyFailures + 1;
659 if (inputResult.type != TestResultType::Pass &&
660 runCount < static_cast<uint32_t>(flakyRetries))
661 {
662 inputResult.type = TestResultType::NoResult;
663 outputResult.flakyFailures++;
664 }
665 else
666 {
667 outputResult.elapsedTimeSeconds = inputResult.elapsedTimeSeconds;
668 outputResult.type = inputResult.type;
669 }
670 }
671 }
672
673 return true;
674 }
675
PrintTestOutputSnippet(const TestIdentifier & id,const TestResult & result,const std::string & fullOutput)676 void PrintTestOutputSnippet(const TestIdentifier &id,
677 const TestResult &result,
678 const std::string &fullOutput)
679 {
680 std::stringstream nameStream;
681 nameStream << id;
682 std::string fullName = nameStream.str();
683
684 size_t runPos = fullOutput.find(std::string(kStartedTestString) + fullName);
685 if (runPos == std::string::npos)
686 {
687 printf("Cannot locate test output snippet.\n");
688 return;
689 }
690
691 size_t endPos = fullOutput.find(std::string(kFailedTestString) + fullName, runPos);
692 // Only clip the snippet to the "OK" message if the test really
693 // succeeded. It still might have e.g. crashed after printing it.
694 if (endPos == std::string::npos && result.type == TestResultType::Pass)
695 {
696 endPos = fullOutput.find(std::string(kPassedTestString) + fullName, runPos);
697 }
698 if (endPos != std::string::npos)
699 {
700 size_t newline_pos = fullOutput.find("\n", endPos);
701 if (newline_pos != std::string::npos)
702 endPos = newline_pos + 1;
703 }
704
705 std::cout << "\n";
706 if (endPos != std::string::npos)
707 {
708 std::cout << fullOutput.substr(runPos, endPos - runPos);
709 }
710 else
711 {
712 std::cout << fullOutput.substr(runPos);
713 }
714 }
715
GetConfigNameFromTestIdentifier(const TestIdentifier & id)716 std::string GetConfigNameFromTestIdentifier(const TestIdentifier &id)
717 {
718 size_t slashPos = id.testName.find('/');
719 if (slashPos == std::string::npos)
720 {
721 return "default";
722 }
723
724 size_t doubleUnderscorePos = id.testName.find("__");
725 if (doubleUnderscorePos == std::string::npos)
726 {
727 std::string configName = id.testName.substr(slashPos + 1);
728
729 if (!BeginsWith(configName, "ES"))
730 {
731 return "default";
732 }
733
734 return configName;
735 }
736 else
737 {
738 return id.testName.substr(slashPos + 1, doubleUnderscorePos - slashPos - 1);
739 }
740 }
741
BatchTests(const std::vector<TestIdentifier> & tests,int batchSize)742 TestQueue BatchTests(const std::vector<TestIdentifier> &tests, int batchSize)
743 {
744 // First sort tests by configuration.
745 std::unordered_map<std::string, std::vector<TestIdentifier>> testsSortedByConfig;
746 for (const TestIdentifier &id : tests)
747 {
748 std::string config = GetConfigNameFromTestIdentifier(id);
749 testsSortedByConfig[config].push_back(id);
750 }
751
752 // Then group into batches by 'batchSize'.
753 TestQueue testQueue;
754 for (const auto &configAndIds : testsSortedByConfig)
755 {
756 const std::vector<TestIdentifier> &configTests = configAndIds.second;
757
758 // Count the number of batches needed for this config.
759 int batchesForConfig = static_cast<int>(configTests.size() + batchSize - 1) / batchSize;
760
761 // Create batches with striping to split up slow tests.
762 for (int batchIndex = 0; batchIndex < batchesForConfig; ++batchIndex)
763 {
764 std::vector<TestIdentifier> batchTests;
765 for (size_t testIndex = batchIndex; testIndex < configTests.size();
766 testIndex += batchesForConfig)
767 {
768 batchTests.push_back(configTests[testIndex]);
769 }
770 testQueue.emplace(std::move(batchTests));
771 ASSERT(batchTests.empty());
772 }
773 }
774
775 return testQueue;
776 }
777
ListTests(const std::map<TestIdentifier,TestResult> & resultsMap)778 void ListTests(const std::map<TestIdentifier, TestResult> &resultsMap)
779 {
780 std::map<std::string, std::vector<std::string>> suites;
781
782 std::cout << "Tests list:\n";
783
784 for (const auto &resultIt : resultsMap)
785 {
786 const TestIdentifier &id = resultIt.first;
787 std::cout << id << "\n";
788 }
789 }
790
791 // Prints the names of the tests matching the user-specified filter flag.
792 // This matches the output from googletest/src/gtest.cc but is much much faster for large filters.
793 // See http://anglebug.com/5164
GTestListTests(const std::map<TestIdentifier,TestResult> & resultsMap)794 void GTestListTests(const std::map<TestIdentifier, TestResult> &resultsMap)
795 {
796 std::map<std::string, std::vector<std::string>> suites;
797
798 for (const auto &resultIt : resultsMap)
799 {
800 const TestIdentifier &id = resultIt.first;
801 suites[id.testSuiteName].push_back(id.testName);
802 }
803
804 for (const auto &testSuiteIt : suites)
805 {
806 bool printedTestSuiteName = false;
807
808 const std::string &suiteName = testSuiteIt.first;
809 const std::vector<std::string> &testNames = testSuiteIt.second;
810
811 for (const std::string &testName : testNames)
812 {
813 if (!printedTestSuiteName)
814 {
815 printedTestSuiteName = true;
816 printf("%s.\n", suiteName.c_str());
817 }
818 printf(" %s\n", testName.c_str());
819 }
820 }
821 }
822 } // namespace
823
824 // static
825 TestSuite *TestSuite::mInstance = nullptr;
826
827 TestIdentifier::TestIdentifier() = default;
828
TestIdentifier(const std::string & suiteNameIn,const std::string & nameIn)829 TestIdentifier::TestIdentifier(const std::string &suiteNameIn, const std::string &nameIn)
830 : testSuiteName(suiteNameIn), testName(nameIn)
831 {}
832
833 TestIdentifier::TestIdentifier(const TestIdentifier &other) = default;
834
835 TestIdentifier::~TestIdentifier() = default;
836
837 TestIdentifier &TestIdentifier::operator=(const TestIdentifier &other) = default;
838
sprintfName(char * outBuffer) const839 void TestIdentifier::sprintfName(char *outBuffer) const
840 {
841 sprintf(outBuffer, "%s.%s", testSuiteName.c_str(), testName.c_str());
842 }
843
844 // static
ParseFromString(const std::string & str,TestIdentifier * idOut)845 bool TestIdentifier::ParseFromString(const std::string &str, TestIdentifier *idOut)
846 {
847 size_t separator = str.find(".");
848 if (separator == std::string::npos)
849 {
850 return false;
851 }
852
853 idOut->testSuiteName = str.substr(0, separator);
854 idOut->testName = str.substr(separator + 1, str.length() - separator - 1);
855 return true;
856 }
857
858 TestResults::TestResults() = default;
859
860 TestResults::~TestResults() = default;
861
862 ProcessInfo::ProcessInfo() = default;
863
operator =(ProcessInfo && rhs)864 ProcessInfo &ProcessInfo::operator=(ProcessInfo &&rhs)
865 {
866 process = std::move(rhs.process);
867 testsInBatch = std::move(rhs.testsInBatch);
868 resultsFileName = std::move(rhs.resultsFileName);
869 filterFileName = std::move(rhs.filterFileName);
870 commandLine = std::move(rhs.commandLine);
871 filterString = std::move(rhs.filterString);
872 return *this;
873 }
874
875 ProcessInfo::~ProcessInfo() = default;
876
ProcessInfo(ProcessInfo && other)877 ProcessInfo::ProcessInfo(ProcessInfo &&other)
878 {
879 *this = std::move(other);
880 }
881
TestSuite(int * argc,char ** argv)882 TestSuite::TestSuite(int *argc, char **argv)
883 : mShardCount(-1),
884 mShardIndex(-1),
885 mBotMode(false),
886 mDebugTestGroups(false),
887 mGTestListTests(false),
888 mListTests(false),
889 mPrintTestStdout(false),
890 mDisableCrashHandler(false),
891 mBatchSize(kDefaultBatchSize),
892 mCurrentResultCount(0),
893 mTotalResultCount(0),
894 mMaxProcesses(std::min(NumberOfProcessors(), kDefaultMaxProcesses)),
895 mTestTimeout(kDefaultTestTimeout),
896 mBatchTimeout(kDefaultBatchTimeout),
897 mBatchId(-1),
898 mFlakyRetries(0)
899 {
900 ASSERT(mInstance == nullptr);
901 mInstance = this;
902
903 Optional<int> filterArgIndex;
904 bool alsoRunDisabledTests = false;
905
906 #if defined(ANGLE_PLATFORM_WINDOWS)
907 testing::GTEST_FLAG(catch_exceptions) = false;
908 #endif
909
910 if (*argc <= 0)
911 {
912 printf("Missing test arguments.\n");
913 exit(EXIT_FAILURE);
914 }
915
916 mTestExecutableName = argv[0];
917 mTestSuiteName = ParseTestSuiteName(mTestExecutableName.c_str());
918
919 for (int argIndex = 1; argIndex < *argc;)
920 {
921 if (parseSingleArg(argv[argIndex]))
922 {
923 DeleteArg(argc, argv, argIndex);
924 continue;
925 }
926
927 if (ParseFlagValue("--gtest_filter=", argv[argIndex]))
928 {
929 filterArgIndex = argIndex;
930 }
931 else
932 {
933 // Don't include disabled tests in test lists unless the user asks for them.
934 if (strcmp("--gtest_also_run_disabled_tests", argv[argIndex]) == 0)
935 {
936 alsoRunDisabledTests = true;
937 }
938
939 mChildProcessArgs.push_back(argv[argIndex]);
940 }
941 ++argIndex;
942 }
943
944 if (!mDisableCrashHandler)
945 {
946 // Note that the crash callback must be owned and not use global constructors.
947 mCrashCallback = [this]() { onCrashOrTimeout(TestResultType::Crash); };
948 InitCrashHandler(&mCrashCallback);
949 }
950
951 std::string envShardIndex = angle::GetEnvironmentVar("GTEST_SHARD_INDEX");
952 if (!envShardIndex.empty())
953 {
954 angle::UnsetEnvironmentVar("GTEST_SHARD_INDEX");
955 if (mShardIndex == -1)
956 {
957 std::stringstream shardIndexStream(envShardIndex);
958 shardIndexStream >> mShardIndex;
959 }
960 }
961
962 std::string envTotalShards = angle::GetEnvironmentVar("GTEST_TOTAL_SHARDS");
963 if (!envTotalShards.empty())
964 {
965 angle::UnsetEnvironmentVar("GTEST_TOTAL_SHARDS");
966 if (mShardCount == -1)
967 {
968 std::stringstream shardCountStream(envTotalShards);
969 shardCountStream >> mShardCount;
970 }
971 }
972
973 if ((mShardIndex == -1) != (mShardCount == -1))
974 {
975 printf("Shard index and shard count must be specified together.\n");
976 exit(EXIT_FAILURE);
977 }
978
979 if (!mFilterFile.empty())
980 {
981 if (filterArgIndex.valid())
982 {
983 printf("Cannot use gtest_filter in conjunction with a filter file.\n");
984 exit(EXIT_FAILURE);
985 }
986
987 uint32_t fileSize = 0;
988 if (!GetFileSize(mFilterFile.c_str(), &fileSize))
989 {
990 printf("Error getting filter file size: %s\n", mFilterFile.c_str());
991 exit(EXIT_FAILURE);
992 }
993
994 std::vector<char> fileContents(fileSize + 1, 0);
995 if (!ReadEntireFileToString(mFilterFile.c_str(), fileContents.data(), fileSize))
996 {
997 printf("Error loading filter file: %s\n", mFilterFile.c_str());
998 exit(EXIT_FAILURE);
999 }
1000 mFilterString.assign(fileContents.data());
1001
1002 if (mFilterString.substr(0, strlen("--gtest_filter=")) != std::string("--gtest_filter="))
1003 {
1004 printf("Filter file must start with \"--gtest_filter=\".\n");
1005 exit(EXIT_FAILURE);
1006 }
1007
1008 // Note that we only add a filter string if we previously deleted a shader filter file
1009 // argument. So we will have space for the new filter string in argv.
1010 AddArg(argc, argv, mFilterString.c_str());
1011 }
1012
1013 // Call into gtest internals to force parameterized test name registration.
1014 testing::internal::UnitTestImpl *impl = testing::internal::GetUnitTestImpl();
1015 impl->RegisterParameterizedTests();
1016
1017 // Initialize internal GoogleTest filter arguments so we can call "FilterMatchesTest".
1018 testing::internal::ParseGoogleTestFlagsOnly(argc, argv);
1019
1020 std::vector<TestIdentifier> testSet = GetFilteredTests(&mTestFileLines, alsoRunDisabledTests);
1021
1022 if (mShardCount == 0)
1023 {
1024 printf("Shard count must be > 0.\n");
1025 exit(EXIT_FAILURE);
1026 }
1027 else if (mShardCount > 0)
1028 {
1029 if (mShardIndex >= mShardCount)
1030 {
1031 printf("Shard index must be less than shard count.\n");
1032 exit(EXIT_FAILURE);
1033 }
1034
1035 // If there's only one shard, we can use the testSet as defined above.
1036 if (mShardCount > 1)
1037 {
1038 testSet = GetShardTests(testSet, mShardIndex, mShardCount, &mTestFileLines,
1039 alsoRunDisabledTests);
1040
1041 if (!mBotMode)
1042 {
1043 mFilterString = GetTestFilter(testSet);
1044
1045 if (filterArgIndex.valid())
1046 {
1047 argv[filterArgIndex.value()] = const_cast<char *>(mFilterString.c_str());
1048 }
1049 else
1050 {
1051 // Note that we only add a filter string if we previously deleted a shard
1052 // index/count argument. So we will have space for the new filter string in
1053 // argv.
1054 AddArg(argc, argv, mFilterString.c_str());
1055 }
1056
1057 // Force-re-initialize GoogleTest flags to load the shard filter.
1058 testing::internal::ParseGoogleTestFlagsOnly(argc, argv);
1059 }
1060 }
1061 }
1062
1063 if (mBotMode)
1064 {
1065 // Split up test batches.
1066 mTestQueue = BatchTests(testSet, mBatchSize);
1067
1068 if (mDebugTestGroups)
1069 {
1070 std::cout << "Test Groups:\n";
1071
1072 while (!mTestQueue.empty())
1073 {
1074 const std::vector<TestIdentifier> &tests = mTestQueue.front();
1075 std::cout << GetConfigNameFromTestIdentifier(tests[0]) << " ("
1076 << static_cast<int>(tests.size()) << ")\n";
1077 mTestQueue.pop();
1078 }
1079
1080 exit(EXIT_SUCCESS);
1081 }
1082 }
1083
1084 testing::InitGoogleTest(argc, argv);
1085
1086 mTotalResultCount = testSet.size();
1087
1088 if ((mBotMode || !mResultsDirectory.empty()) && mResultsFile.empty())
1089 {
1090 // Create a default output file in bot mode.
1091 mResultsFile = "output.json";
1092 }
1093
1094 if (!mResultsDirectory.empty())
1095 {
1096 std::stringstream resultFileName;
1097 resultFileName << mResultsDirectory << GetPathSeparator() << mResultsFile;
1098 mResultsFile = resultFileName.str();
1099 }
1100
1101 if (!mBotMode)
1102 {
1103 testing::TestEventListeners &listeners = testing::UnitTest::GetInstance()->listeners();
1104 listeners.Append(new TestEventListener(mResultsFile, mHistogramJsonFile,
1105 mTestSuiteName.c_str(), &mTestResults,
1106 &mHistogramWriter));
1107
1108 for (const TestIdentifier &id : testSet)
1109 {
1110 mTestResults.results[id].type = TestResultType::NoResult;
1111 }
1112 }
1113 }
1114
~TestSuite()1115 TestSuite::~TestSuite()
1116 {
1117 if (mWatchdogThread.joinable())
1118 {
1119 mWatchdogThread.detach();
1120 }
1121 TerminateCrashHandler();
1122 }
1123
parseSingleArg(const char * argument)1124 bool TestSuite::parseSingleArg(const char *argument)
1125 {
1126 // Note: Flags should be documented in README.md.
1127 return (ParseIntArg("--shard-count=", argument, &mShardCount) ||
1128 ParseIntArg("--shard-index=", argument, &mShardIndex) ||
1129 ParseIntArg("--batch-size=", argument, &mBatchSize) ||
1130 ParseIntArg("--max-processes=", argument, &mMaxProcesses) ||
1131 ParseIntArg(kTestTimeoutArg, argument, &mTestTimeout) ||
1132 ParseIntArg("--batch-timeout=", argument, &mBatchTimeout) ||
1133 ParseIntArg(kFlakyRetries, argument, &mFlakyRetries) ||
1134 // Other test functions consume the batch ID, so keep it in the list.
1135 ParseIntArgNoDelete(kBatchId, argument, &mBatchId) ||
1136 ParseStringArg("--results-directory=", argument, &mResultsDirectory) ||
1137 ParseStringArg(kResultFileArg, argument, &mResultsFile) ||
1138 ParseStringArg("--isolated-script-test-output=", argument, &mResultsFile) ||
1139 ParseStringArg(kFilterFileArg, argument, &mFilterFile) ||
1140 ParseStringArg(kHistogramJsonFileArg, argument, &mHistogramJsonFile) ||
1141 ParseStringArg("--isolated-script-test-perf-output=", argument, &mHistogramJsonFile) ||
1142 ParseFlag("--bot-mode", argument, &mBotMode) ||
1143 ParseFlag("--debug-test-groups", argument, &mDebugTestGroups) ||
1144 ParseFlag(kGTestListTests, argument, &mGTestListTests) ||
1145 ParseFlag(kListTests, argument, &mListTests) ||
1146 ParseFlag(kPrintTestStdout, argument, &mPrintTestStdout) ||
1147 ParseFlag(kDisableCrashHandler, argument, &mDisableCrashHandler));
1148 }
1149
onCrashOrTimeout(TestResultType crashOrTimeout)1150 void TestSuite::onCrashOrTimeout(TestResultType crashOrTimeout)
1151 {
1152 std::lock_guard<std::mutex> guard(mTestResults.currentTestMutex);
1153 if (mTestResults.currentTest.valid())
1154 {
1155 TestResult &result = mTestResults.results[mTestResults.currentTest];
1156 result.type = crashOrTimeout;
1157 result.elapsedTimeSeconds = mTestResults.currentTestTimer.getElapsedTime();
1158 }
1159
1160 if (mResultsFile.empty())
1161 {
1162 printf("No results file specified.\n");
1163 return;
1164 }
1165
1166 WriteOutputFiles(true, mTestResults, mResultsFile, mHistogramWriter, mHistogramJsonFile,
1167 mTestSuiteName.c_str());
1168 }
1169
launchChildTestProcess(uint32_t batchId,const std::vector<TestIdentifier> & testsInBatch)1170 bool TestSuite::launchChildTestProcess(uint32_t batchId,
1171 const std::vector<TestIdentifier> &testsInBatch)
1172 {
1173 constexpr uint32_t kMaxPath = 1000;
1174
1175 // Create a temporary file to store the test list
1176 ProcessInfo processInfo;
1177
1178 char filterBuffer[kMaxPath] = {};
1179 if (!CreateTemporaryFile(filterBuffer, kMaxPath))
1180 {
1181 std::cerr << "Error creating temporary file for test list.\n";
1182 return false;
1183 }
1184 processInfo.filterFileName.assign(filterBuffer);
1185
1186 std::string filterString = GetTestFilter(testsInBatch);
1187
1188 FILE *fp = fopen(processInfo.filterFileName.c_str(), "w");
1189 if (!fp)
1190 {
1191 std::cerr << "Error opening temporary file for test list.\n";
1192 return false;
1193 }
1194 fprintf(fp, "%s", filterString.c_str());
1195 fclose(fp);
1196
1197 processInfo.filterString = filterString;
1198
1199 std::string filterFileArg = kFilterFileArg + processInfo.filterFileName;
1200
1201 // Create a temporary file to store the test output.
1202 char resultsBuffer[kMaxPath] = {};
1203 if (!CreateTemporaryFile(resultsBuffer, kMaxPath))
1204 {
1205 std::cerr << "Error creating temporary file for test list.\n";
1206 return false;
1207 }
1208 processInfo.resultsFileName.assign(resultsBuffer);
1209
1210 std::string resultsFileArg = kResultFileArg + processInfo.resultsFileName;
1211
1212 // Construct commandline for child process.
1213 std::vector<const char *> args;
1214
1215 args.push_back(mTestExecutableName.c_str());
1216 args.push_back(filterFileArg.c_str());
1217 args.push_back(resultsFileArg.c_str());
1218
1219 std::stringstream batchIdStream;
1220 batchIdStream << kBatchId << batchId;
1221 std::string batchIdString = batchIdStream.str();
1222 args.push_back(batchIdString.c_str());
1223
1224 for (const std::string &arg : mChildProcessArgs)
1225 {
1226 args.push_back(arg.c_str());
1227 }
1228
1229 if (mDisableCrashHandler)
1230 {
1231 args.push_back(kDisableCrashHandler);
1232 }
1233
1234 std::string timeoutStr;
1235 if (mTestTimeout != kDefaultTestTimeout)
1236 {
1237 std::stringstream timeoutStream;
1238 timeoutStream << kTestTimeoutArg << mTestTimeout;
1239 timeoutStr = timeoutStream.str();
1240 args.push_back(timeoutStr.c_str());
1241 }
1242
1243 // Launch child process and wait for completion.
1244 processInfo.process = LaunchProcess(args, true, true);
1245
1246 if (!processInfo.process->started())
1247 {
1248 std::cerr << "Error launching child process.\n";
1249 return false;
1250 }
1251
1252 std::stringstream commandLineStr;
1253 for (const char *arg : args)
1254 {
1255 commandLineStr << arg << " ";
1256 }
1257
1258 processInfo.commandLine = commandLineStr.str();
1259 processInfo.testsInBatch = testsInBatch;
1260 mCurrentProcesses.emplace_back(std::move(processInfo));
1261 return true;
1262 }
1263
ParseTestIdentifierAndSetResult(const std::string & testName,TestResultType result,TestResults * results)1264 void ParseTestIdentifierAndSetResult(const std::string &testName,
1265 TestResultType result,
1266 TestResults *results)
1267 {
1268 // Trim off any whitespace + extra stuff at the end of the string.
1269 std::string modifiedTestName = testName.substr(0, testName.find(' '));
1270 modifiedTestName = modifiedTestName.substr(0, testName.find('\r'));
1271 TestIdentifier id;
1272 bool ok = TestIdentifier::ParseFromString(modifiedTestName, &id);
1273 ASSERT(ok);
1274 results->results[id] = {result};
1275 }
1276
finishProcess(ProcessInfo * processInfo)1277 bool TestSuite::finishProcess(ProcessInfo *processInfo)
1278 {
1279 // Get test results and merge into master list.
1280 TestResults batchResults;
1281
1282 if (!GetTestResultsFromFile(processInfo->resultsFileName.c_str(), &batchResults))
1283 {
1284 std::cerr << "Warning: could not find test results file from child process.\n";
1285
1286 // First assume all tests get skipped.
1287 for (const TestIdentifier &id : processInfo->testsInBatch)
1288 {
1289 batchResults.results[id] = {TestResultType::NoResult};
1290 }
1291
1292 // Attempt to reconstruct passing list from stdout snippets.
1293 const std::string &batchStdout = processInfo->process->getStdout();
1294 std::istringstream linesStream(batchStdout);
1295
1296 std::string line;
1297 while (std::getline(linesStream, line))
1298 {
1299 size_t startPos = line.find(kStartedTestString);
1300 size_t failPos = line.find(kFailedTestString);
1301 size_t passPos = line.find(kPassedTestString);
1302
1303 if (startPos != std::string::npos)
1304 {
1305 // Assume a test that's started crashed until we see it completed.
1306 std::string testName = line.substr(strlen(kStartedTestString));
1307 ParseTestIdentifierAndSetResult(testName, TestResultType::Crash, &batchResults);
1308 }
1309 else if (failPos != std::string::npos)
1310 {
1311 std::string testName = line.substr(strlen(kFailedTestString));
1312 ParseTestIdentifierAndSetResult(testName, TestResultType::Fail, &batchResults);
1313 }
1314 else if (passPos != std::string::npos)
1315 {
1316 std::string testName = line.substr(strlen(kPassedTestString));
1317 ParseTestIdentifierAndSetResult(testName, TestResultType::Pass, &batchResults);
1318 }
1319 }
1320 }
1321
1322 if (!MergeTestResults(&batchResults, &mTestResults, mFlakyRetries))
1323 {
1324 std::cerr << "Error merging batch test results.\n";
1325 return false;
1326 }
1327
1328 if (!batchResults.results.empty())
1329 {
1330 const TestIdentifier &id = batchResults.results.begin()->first;
1331 std::string config = GetConfigNameFromTestIdentifier(id);
1332 printf("Completed batch with config: %s\n", config.c_str());
1333
1334 for (const auto &resultIter : batchResults.results)
1335 {
1336 const TestResult &result = resultIter.second;
1337 if (result.type != TestResultType::NoResult && result.type != TestResultType::Pass)
1338 {
1339 printf("To reproduce the batch, use filter:\n%s\n",
1340 processInfo->filterString.c_str());
1341 break;
1342 }
1343 }
1344 }
1345
1346 // Process results and print unexpected errors.
1347 for (const auto &resultIter : batchResults.results)
1348 {
1349 const TestIdentifier &id = resultIter.first;
1350 const TestResult &result = resultIter.second;
1351
1352 // Skip results aren't procesed since they're added back to the test queue below.
1353 if (result.type == TestResultType::NoResult)
1354 {
1355 continue;
1356 }
1357
1358 mCurrentResultCount++;
1359
1360 printf("[%d/%d] %s.%s", mCurrentResultCount, mTotalResultCount, id.testSuiteName.c_str(),
1361 id.testName.c_str());
1362
1363 if (mPrintTestStdout)
1364 {
1365 const std::string &batchStdout = processInfo->process->getStdout();
1366 PrintTestOutputSnippet(id, result, batchStdout);
1367 }
1368 else if (result.type == TestResultType::Pass)
1369 {
1370 printf(" (%0.1lf ms)\n", result.elapsedTimeSeconds * 1000.0);
1371 }
1372 else if (result.type == TestResultType::Timeout)
1373 {
1374 printf(" (TIMEOUT in %0.1lf s)\n", result.elapsedTimeSeconds);
1375 }
1376 else
1377 {
1378 printf(" (%s)\n", ResultTypeToString(result.type));
1379
1380 const std::string &batchStdout = processInfo->process->getStdout();
1381 PrintTestOutputSnippet(id, result, batchStdout);
1382 }
1383 }
1384
1385 // On unexpected exit, re-queue any unfinished tests.
1386 std::vector<TestIdentifier> unfinishedTests;
1387 for (const auto &resultIter : batchResults.results)
1388 {
1389 const TestIdentifier &id = resultIter.first;
1390 const TestResult &result = resultIter.second;
1391
1392 if (result.type == TestResultType::NoResult)
1393 {
1394 unfinishedTests.push_back(id);
1395 }
1396 }
1397
1398 if (!unfinishedTests.empty())
1399 {
1400 mTestQueue.emplace(std::move(unfinishedTests));
1401 }
1402
1403 // Clean up any dirty temporary files.
1404 for (const std::string &tempFile : {processInfo->filterFileName, processInfo->resultsFileName})
1405 {
1406 // Note: we should be aware that this cleanup won't happen if the harness itself
1407 // crashes. If this situation comes up in the future we should add crash cleanup to the
1408 // harness.
1409 if (!angle::DeleteFile(tempFile.c_str()))
1410 {
1411 std::cerr << "Warning: Error cleaning up temp file: " << tempFile << "\n";
1412 }
1413 }
1414
1415 processInfo->process.reset();
1416 return true;
1417 }
1418
run()1419 int TestSuite::run()
1420 {
1421 if (mListTests)
1422 {
1423 ListTests(mTestResults.results);
1424 return EXIT_SUCCESS;
1425 }
1426
1427 if (mGTestListTests)
1428 {
1429 GTestListTests(mTestResults.results);
1430 return EXIT_SUCCESS;
1431 }
1432
1433 // Run tests serially.
1434 if (!mBotMode)
1435 {
1436 // Only start the watchdog if the debugger is not attached and we're a child process.
1437 if (!angle::IsDebuggerAttached() && mBatchId != -1)
1438 {
1439 startWatchdog();
1440 }
1441 int retVal = RUN_ALL_TESTS();
1442
1443 {
1444 std::lock_guard<std::mutex> guard(mTestResults.currentTestMutex);
1445 mTestResults.allDone = true;
1446 }
1447
1448 if (mWatchdogThread.joinable())
1449 {
1450 mWatchdogThread.join();
1451 }
1452 return retVal;
1453 }
1454
1455 Timer totalRunTime;
1456 totalRunTime.start();
1457
1458 Timer messageTimer;
1459 messageTimer.start();
1460
1461 uint32_t batchId = 0;
1462
1463 while (!mTestQueue.empty() || !mCurrentProcesses.empty())
1464 {
1465 bool progress = false;
1466
1467 // Spawn a process if needed and possible.
1468 if (static_cast<int>(mCurrentProcesses.size()) < mMaxProcesses && !mTestQueue.empty())
1469 {
1470 std::vector<TestIdentifier> testsInBatch = mTestQueue.front();
1471 mTestQueue.pop();
1472
1473 if (!launchChildTestProcess(++batchId, testsInBatch))
1474 {
1475 return 1;
1476 }
1477
1478 progress = true;
1479 }
1480
1481 // Check for process completion.
1482 uint32_t totalTestCount = 0;
1483 for (auto processIter = mCurrentProcesses.begin(); processIter != mCurrentProcesses.end();)
1484 {
1485 ProcessInfo &processInfo = *processIter;
1486 if (processInfo.process->finished())
1487 {
1488 if (!finishProcess(&processInfo))
1489 {
1490 return 1;
1491 }
1492 processIter = mCurrentProcesses.erase(processIter);
1493 progress = true;
1494 }
1495 else if (processInfo.process->getElapsedTimeSeconds() > mBatchTimeout)
1496 {
1497 // Terminate the process and record timeouts for the batch.
1498 // Because we can't determine which sub-test caused a timeout, record the whole
1499 // batch as a timeout failure. Can be improved by using socket message passing.
1500 if (!processInfo.process->kill())
1501 {
1502 return 1;
1503 }
1504 for (const TestIdentifier &testIdentifier : processInfo.testsInBatch)
1505 {
1506 // Because the whole batch failed we can't know how long each test took.
1507 mTestResults.results[testIdentifier].type = TestResultType::Timeout;
1508 }
1509
1510 processIter = mCurrentProcesses.erase(processIter);
1511 progress = true;
1512 }
1513 else
1514 {
1515 totalTestCount += static_cast<uint32_t>(processInfo.testsInBatch.size());
1516 processIter++;
1517 }
1518 }
1519
1520 if (progress)
1521 {
1522 messageTimer.start();
1523 }
1524 else if (messageTimer.getElapsedTime() > kIdleMessageTimeout)
1525 {
1526 const ProcessInfo &processInfo = mCurrentProcesses[0];
1527 double processTime = processInfo.process->getElapsedTimeSeconds();
1528 printf("Running %d tests in %d processes, longest for %d seconds.\n", totalTestCount,
1529 static_cast<int>(mCurrentProcesses.size()), static_cast<int>(processTime));
1530 messageTimer.start();
1531 }
1532
1533 // Sleep briefly and continue.
1534 angle::Sleep(100);
1535 }
1536
1537 // Dump combined results.
1538 WriteOutputFiles(false, mTestResults, mResultsFile, mHistogramWriter, mHistogramJsonFile,
1539 mTestSuiteName.c_str());
1540
1541 totalRunTime.stop();
1542 printf("Tests completed in %lf seconds\n", totalRunTime.getElapsedTime());
1543
1544 return printFailuresAndReturnCount() == 0 ? 0 : 1;
1545 }
1546
printFailuresAndReturnCount() const1547 int TestSuite::printFailuresAndReturnCount() const
1548 {
1549 std::vector<std::string> failures;
1550
1551 for (const auto &resultIter : mTestResults.results)
1552 {
1553 const TestIdentifier &id = resultIter.first;
1554 const TestResult &result = resultIter.second;
1555
1556 if (result.type != TestResultType::Pass)
1557 {
1558 const FileLine &fileLine = mTestFileLines.find(id)->second;
1559
1560 std::stringstream failureMessage;
1561 failureMessage << id << " (" << fileLine.file << ":" << fileLine.line << ") ("
1562 << ResultTypeToString(result.type) << ")";
1563 failures.emplace_back(failureMessage.str());
1564 }
1565 }
1566
1567 if (failures.empty())
1568 return 0;
1569
1570 printf("%zu test%s failed:\n", failures.size(), failures.size() > 1 ? "s" : "");
1571 for (const std::string &failure : failures)
1572 {
1573 printf(" %s\n", failure.c_str());
1574 }
1575
1576 return static_cast<int>(failures.size());
1577 }
1578
startWatchdog()1579 void TestSuite::startWatchdog()
1580 {
1581 auto watchdogMain = [this]() {
1582 do
1583 {
1584 {
1585 std::lock_guard<std::mutex> guard(mTestResults.currentTestMutex);
1586 if (mTestResults.currentTestTimer.getElapsedTime() >
1587 static_cast<double>(mTestTimeout))
1588 {
1589 break;
1590 }
1591
1592 if (mTestResults.allDone)
1593 return;
1594 }
1595
1596 angle::Sleep(500);
1597 } while (true);
1598 onCrashOrTimeout(TestResultType::Timeout);
1599 ::_Exit(EXIT_FAILURE);
1600 };
1601 mWatchdogThread = std::thread(watchdogMain);
1602 }
1603
addHistogramSample(const std::string & measurement,const std::string & story,double value,const std::string & units)1604 void TestSuite::addHistogramSample(const std::string &measurement,
1605 const std::string &story,
1606 double value,
1607 const std::string &units)
1608 {
1609 mHistogramWriter.addSample(measurement, story, value, units);
1610 }
1611
GetTestResultsFromFile(const char * fileName,TestResults * resultsOut)1612 bool GetTestResultsFromFile(const char *fileName, TestResults *resultsOut)
1613 {
1614 std::ifstream ifs(fileName);
1615 if (!ifs.is_open())
1616 {
1617 std::cerr << "Error opening " << fileName << "\n";
1618 return false;
1619 }
1620
1621 js::IStreamWrapper ifsWrapper(ifs);
1622 js::Document document;
1623 document.ParseStream(ifsWrapper);
1624
1625 if (document.HasParseError())
1626 {
1627 std::cerr << "Parse error reading JSON document: " << document.GetParseError() << "\n";
1628 return false;
1629 }
1630
1631 if (!GetTestResultsFromJSON(document, resultsOut))
1632 {
1633 std::cerr << "Error getting test results from JSON.\n";
1634 return false;
1635 }
1636
1637 return true;
1638 }
1639
TestResultTypeToString(TestResultType type)1640 const char *TestResultTypeToString(TestResultType type)
1641 {
1642 switch (type)
1643 {
1644 case TestResultType::Crash:
1645 return "Crash";
1646 case TestResultType::Fail:
1647 return "Fail";
1648 case TestResultType::NoResult:
1649 return "NoResult";
1650 case TestResultType::Pass:
1651 return "Pass";
1652 case TestResultType::Timeout:
1653 return "Timeout";
1654 case TestResultType::Unknown:
1655 return "Unknown";
1656 }
1657 }
1658 } // namespace angle
1659