[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / dali-toolkit-test-utils / test-harness.cpp
1 /*
2  * Copyright (c) 2023 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 GetWChan(int pid)
126 {
127   std::ostringstream procwchan;
128   procwchan << "/proc/" << pid << "/wchan";
129   std::ifstream ifs;
130   ifs.open(procwchan.str(), std::ifstream::in);
131   std::string line;
132   std::getline(ifs, line);
133   ifs.close();
134   return line;
135 }
136
137 std::string ReadAndEscape(std::string filename)
138 {
139   std::ostringstream os;
140   std::ifstream      ifs;
141   ifs.open(filename, std::ifstream::in);
142   while(ifs.good())
143   {
144     std::string line;
145     std::getline(ifs, line);
146     for(auto c : line)
147     {
148       switch(c)
149       {
150         case '<':
151           os << "&lt;";
152           break;
153         case '>':
154           os << "&gt;";
155           break;
156         case '&':
157           os << "&amp;";
158           break;
159         default:
160           os << c;
161           break;
162       }
163     }
164     os << "\\"
165        << "n";
166   }
167   ifs.close();
168   return os.str();
169 }
170
171 void OutputTestResult(
172   std::ofstream& ofs,
173   const char*    pathToExecutable,
174   std::string    testSuiteName,
175   TestCase&      testCase,
176   std::string    startTime,
177   std::string    endTime)
178 {
179   std::string outputFilename = ChildOutputFilename(testCase.childPid);
180   std::string testOutput     = ReadAndEscape(outputFilename);
181
182   ofs << "<testcase component=\"CoreAPI/" << testSuiteName << "/default\" execution_type=\"auto\" id=\""
183       << testCase.name << "\" purpose=\"\" result=\"" << (testCase.result == 0 ? "PASS" : "FAIL") << "\">" << std::endl
184       << "<description><test_script_entry test_script_expected_result=\"0\">"
185       << pathToExecutable << testCase.name << "</test_script_entry>" << std::endl
186       << "</description>"
187       << "<result_info><actual_result>" << (testCase.result == 0 ? "PASS" : "FAIL") << "</actual_result>" << std::endl
188       << "<start>" << startTime << "</start>"
189       << "<end>" << endTime << "</end>"
190       << "<stdout><![CDATA[]]></stdout>"
191       << "<stderr><![CDATA[" << testOutput << "]]></stderr></result_info></testcase>" << std::endl;
192
193   unlink(outputFilename.c_str());
194 }
195
196 void OutputTestResults(const char* processName, RunningTestCases& children)
197 {
198   std::ofstream ofs;
199   std::string   filename   = TestModuleFilename(processName);
200   std::string   moduleName = TestModuleName(processName);
201   ofs.open(filename, std::ofstream::out | std::ofstream::app);
202
203   // Sort completed cases by original test case id
204   std::vector<TestCase> childTestCases;
205   childTestCases.reserve(children.size());
206   for(auto& element : children) childTestCases.push_back(element.second);
207   std::sort(childTestCases.begin(), childTestCases.end(), [](const TestCase& a, const TestCase& b) {
208     return a.testCase < b.testCase;
209   });
210
211   const int BUFSIZE = 256;
212   char      buffer[BUFSIZE];
213   for(auto& testCase : childTestCases)
214   {
215     auto tt = system_clock::to_time_t(testCase.startSystemTime);
216     strftime(buffer, BUFSIZE, "%c", localtime(&tt));
217     std::string startTime(buffer);
218     OutputTestResult(ofs, processName, moduleName, testCase, startTime, startTime);
219   }
220
221   ofs.close();
222 }
223
224 void OutputStatistics(const char* processName, int32_t numPasses, int32_t numFailures)
225 {
226   FILE* fp = fopen("summary.xml", "a");
227   if(fp != NULL)
228   {
229     fprintf(fp,
230             "  <suite name=\"%s-tests\">\n"
231             "    <total_case>%d</total_case>\n"
232             "    <pass_case>%d</pass_case>\n"
233             "    <pass_rate>%5.2f</pass_rate>\n"
234             "    <fail_case>%d</fail_case>\n"
235             "    <fail_rate>%5.2f</fail_rate>\n"
236             "    <block_case>0</block_case>\n"
237             "    <block_rate>0.00</block_rate>\n"
238             "    <na_case>0</na_case>\n"
239             "    <na_rate>0.00</na_rate>\n"
240             "  </suite>\n",
241             basename(processName),
242             numPasses + numFailures,
243             numPasses,
244             numPasses > 0 ? (float)numPasses * 100.0f / (numPasses + numFailures) : 0.0f,
245             numFailures,
246             numPasses > 0 ? (float)numFailures * 100.0f / (numPasses + numFailures) : 0.0f);
247     fclose(fp);
248   }
249 }
250
251 int32_t RunTestCase(struct ::testcase_s& testCase)
252 {
253   int32_t result = EXIT_STATUS_TESTCASE_FAILED;
254
255   // dont want to catch exception as we want to be able to get
256   // gdb stack trace from the first error
257   // by default tests should all always pass with no exceptions
258   if(testCase.startup)
259   {
260     testCase.startup();
261   }
262   try
263   {
264     result = testCase.function();
265   }
266   catch(const char*)
267   {
268     // just catch test fail exception, return is already set to EXIT_STATUS_TESTCASE_FAILED
269   }
270   if(testCase.cleanup)
271   {
272     testCase.cleanup();
273   }
274
275   return result;
276 }
277
278 int32_t RunTestCaseRedirectOutput(TestCase& testCase, bool suppressOutput)
279 {
280   // Executing in child process
281   // Close stdout and stderr to suppress the log output
282   close(STDOUT_FILENO); // File descriptor number for stdout is 1
283
284   // The POSIX specification requires that /dev/null must be provided,
285   // The open function always chooses the lowest unused file descriptor
286   // It is sufficient for stdout to be writable.
287   open("/dev/null", O_WRONLY); // Redirect file descriptor number 1 (i.e. stdout) to /dev/null
288
289   fflush(stderr);
290   close(STDERR_FILENO);
291   if(suppressOutput)
292   {
293     stderr = fopen("/dev/null", "w+"); // Redirect fd 2 to /dev/null
294   }
295   else
296   {
297     // When stderr is opened it must be both readable and writable.
298     std::string childOutputFilename = ChildOutputFilename(getpid());
299     stderr                          = fopen(childOutputFilename.c_str(), "w+");
300   }
301
302   int32_t status = RunTestCase(*testCase.tctPtr);
303
304   fflush(stderr);
305   fclose(stderr);
306
307   return status;
308 }
309
310 int32_t RunTestCaseInChildProcess(TestCase& testCase, bool redirect)
311 {
312   int32_t testResult = EXIT_STATUS_TESTCASE_FAILED;
313
314   int32_t pid = fork();
315   if(pid == 0) // Child process
316   {
317     if(redirect)
318     {
319       int status = RunTestCaseRedirectOutput(testCase, false);
320       exit(status);
321     }
322     else
323     {
324       int status = RunTestCase(*testCase.tctPtr);
325       exit(status);
326     }
327   }
328   else if(pid == -1)
329   {
330     perror("fork");
331     exit(EXIT_STATUS_FORK_FAILED);
332   }
333   else // Parent process
334   {
335     int32_t status    = 0;
336     int32_t childPid  = waitpid(pid, &status, 0);
337     testCase.childPid = childPid;
338     if(childPid == -1)
339     {
340       perror("waitpid");
341       exit(EXIT_STATUS_WAITPID_FAILED);
342     }
343     if(WIFEXITED(status))
344     {
345       if(childPid > 0)
346       {
347         testResult = WEXITSTATUS(status);
348         if(testResult)
349         {
350           printf("Test case %s failed: %d\n", testCase.name, testResult);
351         }
352       }
353     }
354     else if(WIFSIGNALED(status))
355     {
356       int32_t signal = WTERMSIG(status);
357       testResult     = EXIT_STATUS_TESTCASE_ABORTED;
358       if(signal == SIGABRT)
359       {
360         printf("Test case %s failed: test case asserted\n", testCase.name);
361       }
362       else
363       {
364         printf("Test case %s failed: exit with signal %s\n", testCase.name, strsignal(WTERMSIG(status)));
365       }
366     }
367     else if(WIFSTOPPED(status))
368     {
369       printf("Test case %s failed: stopped with signal %s\n", testCase.name, strsignal(WSTOPSIG(status)));
370     }
371   }
372   fflush(stdout);
373   fflush(stderr);
374   return testResult;
375 }
376
377 int32_t RunAll(const char* processName, ::testcase tc_array[], std::string match, bool quiet)
378 {
379   int32_t       numFailures = 0;
380   int32_t       numPasses   = 0;
381   std::ofstream ofs;
382   std::string   filename   = TestModuleFilename(processName);
383   std::string   moduleName = TestModuleName(processName);
384   ofs.open(filename, std::ofstream::out | std::ofstream::app);
385   const int BUFSIZE = 256;
386   char      buffer[BUFSIZE];
387
388   // Run test cases in child process( to handle signals ), but run serially.
389   for(uint32_t i = 0; tc_array[i].name; i++)
390   {
391     auto tt = system_clock::to_time_t(system_clock::now());
392     strftime(buffer, BUFSIZE, "%c", localtime(&tt));
393     std::string startTime(buffer);
394
395     TestCase testCase(i, &tc_array[i]);
396     bool     run = true;
397     if(!match.empty())
398     {
399       if(match.compare(0, match.size(), testCase.name, match.size()))
400       {
401         run = false;
402       }
403     }
404
405     if(run)
406     {
407       testCase.result = RunTestCaseInChildProcess(testCase, quiet);
408
409       tt = system_clock::to_time_t(system_clock::now());
410       strftime(buffer, BUFSIZE, "%c", localtime(&tt));
411       std::string endTime(buffer);
412
413       if(testCase.result == 0)
414       {
415         numPasses++;
416       }
417       else
418       {
419         numFailures++;
420       }
421       if(!quiet)
422       {
423         OutputTestResult(ofs, processName, moduleName, testCase, startTime, endTime);
424       }
425     }
426   }
427   ofs.close();
428
429   OutputStatistics(processName, numPasses, numFailures);
430
431   return numFailures;
432 }
433
434 // Constantly runs up to MAX_NUM_CHILDREN processes
435 int32_t RunAllInParallel(const char* processName, ::testcase tc_array[], std::string match, bool reRunFailed, bool quiet)
436 {
437   int32_t numFailures = 0;
438   int32_t numPasses   = 0;
439
440   RunningTestCases     children;
441   std::vector<int32_t> failedTestCases;
442
443   // Fork up to MAX_NUM_CHILDREN processes, then
444   // wait. As soon as a proc completes, fork the next.
445
446   int32_t nextTestCase       = 0;
447   int32_t numRunningChildren = 0;
448
449   while(tc_array[nextTestCase].name || numRunningChildren > 0)
450   {
451     // Create more children (up to the max number or til the end of the array)
452     while(numRunningChildren < MAX_NUM_CHILDREN && tc_array[nextTestCase].name)
453     {
454       bool run = true;
455       if(!match.empty())
456       {
457         if(match.compare(0, match.size(), tc_array[nextTestCase].name, match.size()))
458         {
459           run = false;
460         }
461       }
462       if(run)
463       {
464         int32_t pid = fork();
465         if(pid == 0) // Child process
466         {
467           TestCase testCase(nextTestCase, &tc_array[nextTestCase]);
468           int      status = RunTestCaseRedirectOutput(testCase, quiet);
469           exit(status);
470         }
471         else if(pid == -1)
472         {
473           perror("fork");
474           exit(EXIT_STATUS_FORK_FAILED);
475         }
476         else // Parent process
477         {
478           TestCase tc(nextTestCase, tc_array[nextTestCase].name);
479           tc.startTime       = steady_clock::now();
480           tc.startSystemTime = system_clock::now();
481           tc.childPid        = pid;
482
483           children[pid] = tc;
484           nextTestCase++;
485           numRunningChildren++;
486         }
487       }
488       else
489       {
490         nextTestCase++;
491       }
492     }
493
494     int32_t status   = 0;
495     int32_t childPid = 0;
496     if(numRunningChildren > 0) // Only wait if there are running children.
497     {
498       // Check to see if any children have finished yet
499       childPid = waitpid(-1, &status, WNOHANG);
500     }
501
502     if(childPid == 0)
503     {
504       // No children have finished.
505       // Check if any have exceeded execution time
506       auto endTime = std::chrono::steady_clock::now();
507
508       for(auto& tc : children)
509       {
510         std::chrono::steady_clock::duration timeSpan = endTime - tc.second.startTime;
511         double                              seconds  = double(timeSpan.count()) * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den;
512
513         if(4.9999 < seconds && seconds < 5 && !tc.second.finished)
514         {
515           printf("Child process %s is delayed: WCHAN:%s\n", tc.second.name, GetWChan(tc.first).c_str());
516         }
517         else if(seconds > MAXIMUM_CHILD_LIFETIME)
518         {
519           // Kill the child process. A subsequent call to waitpid will process signal result below.
520           if(!tc.second.finished)
521           {
522             printf("Child process %s WCHAN:%s\n", tc.second.name, GetWChan(tc.first).c_str());
523             kill(tc.first, SIGKILL);
524             tc.second.finished = true; // Only send kill signal once.
525           }
526         }
527       }
528     }
529     else if(childPid == -1) // waitpid errored
530     {
531       perror("waitpid");
532       exit(EXIT_STATUS_WAITPID_FAILED);
533     }
534     else // a child has finished
535     {
536       if(WIFEXITED(status))
537       {
538         auto& testCase    = children[childPid];
539         testCase.result   = WEXITSTATUS(status);
540         testCase.finished = true;
541         if(testCase.result)
542         {
543           printf("Test case %s failed: %d\n", testCase.name, testCase.result);
544           failedTestCases.push_back(testCase.testCase);
545           numFailures++;
546         }
547         else
548         {
549           numPasses++;
550         }
551         numRunningChildren--;
552       }
553
554       else if(WIFSIGNALED(status) || WIFSTOPPED(status))
555       {
556         status = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status);
557
558         RunningTestCases::iterator iter = children.find(childPid);
559         if(iter != children.end())
560         {
561           iter->second.finished = true;
562           printf("Test case %s exited with signal %s\n", iter->second.name, strsignal(status));
563           iter->second.result = 1;
564           failedTestCases.push_back(iter->second.testCase);
565         }
566         else
567         {
568           printf("Unknown child process: %d signaled %s\n", childPid, strsignal(status));
569         }
570
571         numFailures++;
572         numRunningChildren--;
573       }
574     }
575   }
576
577   if(!quiet)
578   {
579     OutputTestResults(processName, children);
580   }
581
582   OutputStatistics(processName, numPasses, numFailures);
583
584   if(reRunFailed)
585   {
586     for(uint32_t i = 0; i < failedTestCases.size(); i++)
587     {
588       char*   testCaseStrapline;
589       int32_t numChars = asprintf(&testCaseStrapline, "Test case %s", tc_array[failedTestCases[i]].name);
590       printf("\n%s\n", testCaseStrapline);
591       for(int32_t j = 0; j < numChars; j++)
592       {
593         printf("=");
594       }
595       printf("\n");
596       int      index = failedTestCases[i];
597       TestCase testCase(index, &tc_array[index]);
598       RunTestCaseInChildProcess(testCase, false);
599     }
600   }
601
602   return numFailures;
603 }
604
605 int32_t FindAndRunTestCase(::testcase tc_array[], const char* testCaseName)
606 {
607   int32_t result = EXIT_STATUS_TESTCASE_NOT_FOUND;
608
609   for(int32_t i = 0; tc_array[i].name; i++)
610   {
611     if(!strcmp(testCaseName, tc_array[i].name))
612     {
613       return RunTestCase(tc_array[i]);
614     }
615   }
616
617   printf("Unknown testcase name: \"%s\"\n", testCaseName);
618   return result;
619 }
620
621 void Usage(const char* program)
622 {
623   printf(
624     "Usage: \n"
625     "   %s <testcase name>\t\t Execute a test case\n"
626     "   %s \t\t Execute all test cases in parallel\n"
627     "   %s -r\t\t Execute all test cases in parallel, rerunning failed test cases\n"
628     "   %s -s\t\t Execute all test cases serially\n"
629     "   %s -q\t\t Run without output\n",
630     program,
631     program,
632     program,
633     program,
634     program);
635 }
636
637 int RunTests(int argc, char* const argv[], ::testcase tc_array[])
638 {
639   int         result    = TestHarness::EXIT_STATUS_BAD_ARGUMENT;
640   const char* optString = "sfqm:";
641   bool        optRerunFailed(true);
642   bool        optRunSerially(false);
643   bool        optQuiet(false);
644   std::string optMatch;
645
646   int nextOpt = 0;
647   do
648   {
649     nextOpt = getopt(argc, argv, optString);
650     switch(nextOpt)
651     {
652       case 'f':
653         optRerunFailed = false;
654         break;
655       case 's':
656         optRunSerially = true;
657         break;
658       case 'q':
659         optQuiet = true;
660         break;
661       case 'm':
662         optMatch = optarg;
663         break;
664       case '?':
665         TestHarness::Usage(argv[0]);
666         exit(TestHarness::EXIT_STATUS_BAD_ARGUMENT);
667         break;
668     }
669   } while(nextOpt != -1);
670
671   if(optind == argc) // no testcase name in argument list
672   {
673     if(optRunSerially)
674     {
675       result = TestHarness::RunAll(argv[0], tc_array, optMatch, optQuiet);
676     }
677     else
678     {
679       result = TestHarness::RunAllInParallel(argv[0], tc_array, optMatch, optRerunFailed, optQuiet);
680     }
681   }
682   else
683   {
684     // optind is index of next argument - interpret as testcase name
685     result = TestHarness::FindAndRunTestCase(tc_array, argv[optind]);
686   }
687   return result;
688 }
689
690 } // namespace TestHarness