Added prefix matching to test case execution
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / dali-toolkit-test-utils / test-harness.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "test-harness.h"
18
19 #include <fcntl.h>
20 #include <stdlib.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <testcase.h>
24
25 #include <getopt.h>
26 #include <unistd.h>
27 #include <algorithm>
28 #include <chrono>
29 #include <cstdlib>
30 #include <cstring>
31 #include <ctime>
32 #include <fstream>
33 #include <map>
34 #include <sstream>
35 #include <vector>
36
37 using std::chrono::steady_clock;
38 using std::chrono::system_clock;
39
40 namespace TestHarness
41 {
42 typedef std::map<int32_t, TestCase> RunningTestCases;
43
44 const double MAXIMUM_CHILD_LIFETIME(60.0f); // 1 minute
45
46 const char* basename(const char* path)
47 {
48   const char* ptr   = path;
49   const char* slash = NULL;
50   for(; *ptr != '\0'; ++ptr)
51   {
52     if(*ptr == '/') slash = ptr;
53   }
54   if(slash != NULL) ++slash;
55   return slash;
56 }
57
58 std::vector<std::string> Split(const std::string& aString, char delimiter)
59 {
60   std::vector<std::string> tokens;
61   std::string              token;
62   std::istringstream       tokenStream(aString);
63   while(std::getline(tokenStream, token, delimiter))
64   {
65     tokens.push_back(token);
66   }
67   return tokens;
68 }
69
70 std::string Join(const std::vector<std::string>& tokens, char delimiter)
71 {
72   std::ostringstream oss;
73
74   unsigned int delimiterCount = 0;
75   for(auto& token : tokens)
76   {
77     oss << token;
78     if(delimiterCount < tokens.size() - 1)
79     {
80       oss << delimiter;
81     }
82     ++delimiterCount;
83   }
84   return oss.str();
85 }
86
87 std::string ChildOutputFilename(int pid)
88 {
89   std::ostringstream os;
90   os << "/tmp/tct-child." << pid;
91   return os.str();
92 }
93
94 std::string TestModuleFilename(const char* processName)
95 {
96   auto pathComponents = Split(processName, '/');
97   auto aModule        = pathComponents.back();
98   aModule += "-tests.xml";
99   return aModule;
100 }
101
102 std::string TestModuleName(const char* processName)
103 {
104   auto pathComponents   = Split(processName, '/');
105   auto aModule          = pathComponents.back();
106   auto moduleComponents = Split(aModule, '-');
107
108   moduleComponents[1][0] = std::toupper(moduleComponents[1][0]);
109   moduleComponents[2][0] = std::toupper(moduleComponents[2][0]);
110
111   std::ostringstream oss;
112   for(unsigned int i = 1; i < moduleComponents.size() - 1; ++i) // [0]=tct, [n-1]=core
113   {
114     oss << moduleComponents[i];
115
116     if(i > 1 && i < moduleComponents.size() - 2) // skip first and last delimiter
117     {
118       oss << '-';
119     }
120   }
121
122   return oss.str();
123 }
124
125 std::string ReadAndEscape(std::string filename)
126 {
127   std::ostringstream os;
128   std::ifstream      ifs;
129   ifs.open(filename, std::ifstream::in);
130   while(ifs.good())
131   {
132     std::string line;
133     std::getline(ifs, line);
134     for(auto c : line)
135     {
136       switch(c)
137       {
138         case '<':
139           os << "&lt;";
140           break;
141         case '>':
142           os << "&gt;";
143           break;
144         case '&':
145           os << "&amp;";
146           break;
147         default:
148           os << c;
149           break;
150       }
151     }
152     os << "\\"
153        << "n";
154   }
155   ifs.close();
156   return os.str();
157 }
158
159 void OutputTestResult(
160   std::ofstream& ofs,
161   const char*    pathToExecutable,
162   std::string    testSuiteName,
163   TestCase&      testCase,
164   std::string    startTime,
165   std::string    endTime)
166 {
167   std::string outputFilename = ChildOutputFilename(testCase.childPid);
168   std::string testOutput     = ReadAndEscape(outputFilename);
169
170   ofs << "<testcase component=\"CoreAPI/" << testSuiteName << "/default\" execution_type=\"auto\" id=\""
171       << testCase.name << "\" purpose=\"\" result=\"" << (testCase.result == 0 ? "PASS" : "FAIL") << "\">" << std::endl
172       << "<description><test_script_entry test_script_expected_result=\"0\">"
173       << pathToExecutable << testCase.name << "</test_script_entry>" << std::endl
174       << "</description>"
175       << "<result_info><actual_result>" << (testCase.result == 0 ? "PASS" : "FAIL") << "</actual_result>" << std::endl
176       << "<start>" << startTime << "</start>"
177       << "<end>" << endTime << "</end>"
178       << "<stdout><![CDATA[]]></stdout>"
179       << "<stderr><![CDATA[" << testOutput << "]]></stderr></result_info></testcase>" << std::endl;
180
181   unlink(outputFilename.c_str());
182 }
183
184 void OutputTestResults(const char* processName, RunningTestCases& children)
185 {
186   std::ofstream ofs;
187   std::string   filename   = TestModuleFilename(processName);
188   std::string   moduleName = TestModuleName(processName);
189   ofs.open(filename, std::ofstream::out | std::ofstream::app);
190
191   // Sort completed cases by original test case id
192   std::vector<TestCase> childTestCases;
193   childTestCases.reserve(children.size());
194   for(auto& element : children) childTestCases.push_back(element.second);
195   std::sort(childTestCases.begin(), childTestCases.end(), [](const TestCase& a, const TestCase& b) {
196     return a.testCase < b.testCase;
197   });
198
199   const int BUFSIZE = 256;
200   char      buffer[BUFSIZE];
201   for(auto& testCase : childTestCases)
202   {
203     auto tt = system_clock::to_time_t(testCase.startSystemTime);
204     strftime(buffer, BUFSIZE, "%c", localtime(&tt));
205     std::string startTime(buffer);
206     OutputTestResult(ofs, processName, moduleName, testCase, startTime, startTime);
207   }
208
209   ofs.close();
210 }
211
212 void OutputStatistics(const char* processName, int32_t numPasses, int32_t numFailures)
213 {
214   FILE* fp = fopen("summary.xml", "a");
215   if(fp != NULL)
216   {
217     fprintf(fp,
218             "  <suite name=\"%s-tests\">\n"
219             "    <total_case>%d</total_case>\n"
220             "    <pass_case>%d</pass_case>\n"
221             "    <pass_rate>%5.2f</pass_rate>\n"
222             "    <fail_case>%d</fail_case>\n"
223             "    <fail_rate>%5.2f</fail_rate>\n"
224             "    <block_case>0</block_case>\n"
225             "    <block_rate>0.00</block_rate>\n"
226             "    <na_case>0</na_case>\n"
227             "    <na_rate>0.00</na_rate>\n"
228             "  </suite>\n",
229             basename(processName),
230             numPasses + numFailures,
231             numPasses,
232             numPasses > 0 ? (float)numPasses * 100.0f / (numPasses + numFailures) : 0.0f,
233             numFailures,
234             numPasses > 0 ? (float)numFailures * 100.0f / (numPasses + numFailures) : 0.0f);
235     fclose(fp);
236   }
237 }
238
239 int32_t RunTestCase(struct ::testcase_s& testCase)
240 {
241   int32_t result = EXIT_STATUS_TESTCASE_FAILED;
242
243   // dont want to catch exception as we want to be able to get
244   // gdb stack trace from the first error
245   // by default tests should all always pass with no exceptions
246   if(testCase.startup)
247   {
248     testCase.startup();
249   }
250   try
251   {
252     result = testCase.function();
253   }
254   catch(const char*)
255   {
256     // just catch test fail exception, return is already set to EXIT_STATUS_TESTCASE_FAILED
257   }
258   if(testCase.cleanup)
259   {
260     testCase.cleanup();
261   }
262
263   return result;
264 }
265
266 int32_t RunTestCaseRedirectOutput(TestCase& testCase, bool suppressOutput)
267 {
268   // Executing in child process
269   // Close stdout and stderr to suppress the log output
270   close(STDOUT_FILENO); // File descriptor number for stdout is 1
271
272   // The POSIX specification requires that /dev/null must be provided,
273   // The open function always chooses the lowest unused file descriptor
274   // It is sufficient for stdout to be writable.
275   open("/dev/null", O_WRONLY); // Redirect file descriptor number 1 (i.e. stdout) to /dev/null
276
277   fflush(stderr);
278   close(STDERR_FILENO);
279   if(suppressOutput)
280   {
281     stderr = fopen("/dev/null", "w+"); // Redirect fd 2 to /dev/null
282   }
283   else
284   {
285     // When stderr is opened it must be both readable and writable.
286     std::string childOutputFilename = ChildOutputFilename(getpid());
287     stderr                          = fopen(childOutputFilename.c_str(), "w+");
288   }
289
290   int32_t status = RunTestCase(*testCase.tctPtr);
291
292   fflush(stderr);
293   fclose(stderr);
294
295   return status;
296 }
297
298 int32_t RunTestCaseInChildProcess(TestCase& testCase, bool redirect)
299 {
300   int32_t testResult = EXIT_STATUS_TESTCASE_FAILED;
301
302   int32_t pid = fork();
303   if(pid == 0) // Child process
304   {
305     if(redirect)
306     {
307       int status = RunTestCaseRedirectOutput(testCase, false);
308       exit(status);
309     }
310     else
311     {
312       int status = RunTestCase(*testCase.tctPtr);
313       exit(status);
314     }
315   }
316   else if(pid == -1)
317   {
318     perror("fork");
319     exit(EXIT_STATUS_FORK_FAILED);
320   }
321   else // Parent process
322   {
323     int32_t status    = 0;
324     int32_t childPid  = waitpid(pid, &status, 0);
325     testCase.childPid = childPid;
326     if(childPid == -1)
327     {
328       perror("waitpid");
329       exit(EXIT_STATUS_WAITPID_FAILED);
330     }
331     if(WIFEXITED(status))
332     {
333       if(childPid > 0)
334       {
335         testResult = WEXITSTATUS(status);
336         if(testResult)
337         {
338           printf("Test case %s failed: %d\n", testCase.name, testResult);
339         }
340       }
341     }
342     else if(WIFSIGNALED(status))
343     {
344       int32_t signal = WTERMSIG(status);
345       testResult     = EXIT_STATUS_TESTCASE_ABORTED;
346       if(signal == SIGABRT)
347       {
348         printf("Test case %s failed: test case asserted\n", testCase.name);
349       }
350       else
351       {
352         printf("Test case %s failed: exit with signal %s\n", testCase.name, strsignal(WTERMSIG(status)));
353       }
354     }
355     else if(WIFSTOPPED(status))
356     {
357       printf("Test case %s failed: stopped with signal %s\n", testCase.name, strsignal(WSTOPSIG(status)));
358     }
359   }
360   fflush(stdout);
361   fflush(stderr);
362   return testResult;
363 }
364
365 int32_t RunAll(const char* processName, ::testcase tc_array[], std::string match, bool quiet)
366 {
367   int32_t       numFailures = 0;
368   int32_t       numPasses   = 0;
369   std::ofstream ofs;
370   std::string   filename   = TestModuleFilename(processName);
371   std::string   moduleName = TestModuleName(processName);
372   ofs.open(filename, std::ofstream::out | std::ofstream::app);
373   const int BUFSIZE = 256;
374   char      buffer[BUFSIZE];
375
376   // Run test cases in child process( to handle signals ), but run serially.
377   for(uint32_t i = 0; tc_array[i].name; i++)
378   {
379     auto tt = system_clock::to_time_t(system_clock::now());
380     strftime(buffer, BUFSIZE, "%c", localtime(&tt));
381     std::string startTime(buffer);
382
383     TestCase testCase(i, &tc_array[i]);
384     bool     run = true;
385     if(!match.empty())
386     {
387       if(match.compare(0, match.size(), testCase.name, match.size()))
388       {
389         run = false;
390       }
391     }
392
393     if(run)
394     {
395       testCase.result = RunTestCaseInChildProcess(testCase, quiet);
396
397       tt = system_clock::to_time_t(system_clock::now());
398       strftime(buffer, BUFSIZE, "%c", localtime(&tt));
399       std::string endTime(buffer);
400
401       if(testCase.result == 0)
402       {
403         numPasses++;
404       }
405       else
406       {
407         numFailures++;
408       }
409       if(!quiet)
410       {
411         OutputTestResult(ofs, processName, moduleName, testCase, startTime, endTime);
412       }
413     }
414   }
415   ofs.close();
416
417   OutputStatistics(processName, numPasses, numFailures);
418
419   return numFailures;
420 }
421
422 // Constantly runs up to MAX_NUM_CHILDREN processes
423 int32_t RunAllInParallel(const char* processName, ::testcase tc_array[], std::string match, bool reRunFailed, bool quiet)
424 {
425   int32_t numFailures = 0;
426   int32_t numPasses   = 0;
427
428   RunningTestCases     children;
429   std::vector<int32_t> failedTestCases;
430
431   // Fork up to MAX_NUM_CHILDREN processes, then
432   // wait. As soon as a proc completes, fork the next.
433
434   int32_t nextTestCase       = 0;
435   int32_t numRunningChildren = 0;
436
437   while(tc_array[nextTestCase].name || numRunningChildren > 0)
438   {
439     // Create more children (up to the max number or til the end of the array)
440     while(numRunningChildren < MAX_NUM_CHILDREN && tc_array[nextTestCase].name)
441     {
442       bool run = true;
443       if(!match.empty())
444       {
445         if(match.compare(0, match.size(), tc_array[nextTestCase].name, match.size()))
446         {
447           run = false;
448         }
449       }
450       if(run)
451       {
452         int32_t pid = fork();
453         if(pid == 0) // Child process
454         {
455           TestCase testCase(nextTestCase, &tc_array[nextTestCase]);
456           int      status = RunTestCaseRedirectOutput(testCase, quiet);
457           exit(status);
458         }
459         else if(pid == -1)
460         {
461           perror("fork");
462           exit(EXIT_STATUS_FORK_FAILED);
463         }
464         else // Parent process
465         {
466           TestCase tc(nextTestCase, tc_array[nextTestCase].name);
467           tc.startTime       = steady_clock::now();
468           tc.startSystemTime = system_clock::now();
469           tc.childPid        = pid;
470
471           children[pid] = tc;
472           nextTestCase++;
473           numRunningChildren++;
474         }
475       }
476       else
477       {
478         nextTestCase++;
479       }
480     }
481
482     int32_t status   = 0;
483     int32_t childPid = 0;
484     if(numRunningChildren > 0) // Only wait if there are running children.
485     {
486       // Check to see if any children have finished yet
487       childPid = waitpid(-1, &status, WNOHANG);
488     }
489
490     if(childPid == 0)
491     {
492       // No children have finished.
493       // Check if any have exceeded execution time
494       auto endTime = std::chrono::steady_clock::now();
495
496       for(auto& tc : children)
497       {
498         std::chrono::steady_clock::duration timeSpan = endTime - tc.second.startTime;
499         std::chrono::duration<double>       seconds  = std::chrono::duration_cast<std::chrono::duration<double>>(timeSpan);
500         if(seconds.count() > MAXIMUM_CHILD_LIFETIME)
501         {
502           // Kill the child process. A subsequent call to waitpid will process signal result below.
503           kill(tc.first, SIGKILL);
504         }
505       }
506     }
507     else if(childPid == -1) // waitpid errored
508     {
509       perror("waitpid");
510       exit(EXIT_STATUS_WAITPID_FAILED);
511     }
512     else // a child has finished
513     {
514       if(WIFEXITED(status))
515       {
516         auto& testCase  = children[childPid];
517         testCase.result = WEXITSTATUS(status);
518         if(testCase.result)
519         {
520           printf("Test case %s failed: %d\n", testCase.name, testCase.result);
521           failedTestCases.push_back(testCase.testCase);
522           numFailures++;
523         }
524         else
525         {
526           numPasses++;
527         }
528         numRunningChildren--;
529       }
530
531       else if(WIFSIGNALED(status) || WIFSTOPPED(status))
532       {
533         status = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status);
534
535         RunningTestCases::iterator iter = children.find(childPid);
536         if(iter != children.end())
537         {
538           printf("Test case %s exited with signal %s\n", iter->second.name, strsignal(status));
539           iter->second.result = 1;
540           failedTestCases.push_back(iter->second.testCase);
541         }
542         else
543         {
544           printf("Unknown child process: %d signaled %s\n", childPid, strsignal(status));
545         }
546
547         numFailures++;
548         numRunningChildren--;
549       }
550     }
551   }
552
553   if(!quiet)
554   {
555     OutputTestResults(processName, children);
556   }
557
558   OutputStatistics(processName, numPasses, numFailures);
559
560   if(reRunFailed)
561   {
562     for(uint32_t i = 0; i < failedTestCases.size(); i++)
563     {
564       char*   testCaseStrapline;
565       int32_t numChars = asprintf(&testCaseStrapline, "Test case %s", tc_array[failedTestCases[i]].name);
566       printf("\n%s\n", testCaseStrapline);
567       for(int32_t j = 0; j < numChars; j++)
568       {
569         printf("=");
570       }
571       printf("\n");
572       int      index = failedTestCases[i];
573       TestCase testCase(index, &tc_array[index]);
574       RunTestCaseInChildProcess(testCase, false);
575     }
576   }
577
578   return numFailures;
579 }
580
581 int32_t FindAndRunTestCase(::testcase tc_array[], const char* testCaseName)
582 {
583   int32_t result = EXIT_STATUS_TESTCASE_NOT_FOUND;
584
585   for(int32_t i = 0; tc_array[i].name; i++)
586   {
587     if(!strcmp(testCaseName, tc_array[i].name))
588     {
589       return RunTestCase(tc_array[i]);
590     }
591   }
592
593   printf("Unknown testcase name: \"%s\"\n", testCaseName);
594   return result;
595 }
596
597 void Usage(const char* program)
598 {
599   printf(
600     "Usage: \n"
601     "   %s <testcase name>\t\t Execute a test case\n"
602     "   %s \t\t Execute all test cases in parallel\n"
603     "   %s -r\t\t Execute all test cases in parallel, rerunning failed test cases\n"
604     "   %s -s\t\t Execute all test cases serially\n"
605     "   %s -q\t\t Run without output\n",
606     program,
607     program,
608     program,
609     program,
610     program);
611 }
612
613 int RunTests(int argc, char* const argv[], ::testcase tc_array[])
614 {
615   int         result    = TestHarness::EXIT_STATUS_BAD_ARGUMENT;
616   const char* optString = "sfqm:";
617   bool        optRerunFailed(true);
618   bool        optRunSerially(false);
619   bool        optQuiet(false);
620   std::string optMatch;
621
622   int nextOpt = 0;
623   do
624   {
625     nextOpt = getopt(argc, argv, optString);
626     switch(nextOpt)
627     {
628       case 'f':
629         optRerunFailed = false;
630         break;
631       case 's':
632         optRunSerially = true;
633         break;
634       case 'q':
635         optQuiet = true;
636         break;
637       case 'm':
638         optMatch = optarg;
639         break;
640       case '?':
641         TestHarness::Usage(argv[0]);
642         exit(TestHarness::EXIT_STATUS_BAD_ARGUMENT);
643         break;
644     }
645   } while(nextOpt != -1);
646
647   if(optind == argc) // no testcase name in argument list
648   {
649     if(optRunSerially)
650     {
651       result = TestHarness::RunAll(argv[0], tc_array, optMatch, optQuiet);
652     }
653     else
654     {
655       result = TestHarness::RunAllInParallel(argv[0], tc_array, optMatch, optRerunFailed, optQuiet);
656     }
657   }
658   else
659   {
660     // optind is index of next argument - interpret as testcase name
661     result = TestHarness::FindAndRunTestCase(tc_array, argv[optind]);
662   }
663   return result;
664 }
665
666 } // namespace TestHarness