Fix the build fail of toolkit-test-util
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / dali-toolkit-test-utils / test-harness.cpp
1 /*
2  * Copyright (c) 2020 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 <time.h>
26 #include <unistd.h>
27
28 #include <chrono>
29 #include <cstring>
30 #include <map>
31 #include <vector>
32
33 namespace TestHarness
34 {
35 typedef std::map<int32_t, TestCase> RunningTestCases;
36
37 const double MAXIMUM_CHILD_LIFETIME(60.0f); // 1 minute
38
39 const char* basename(const char* path)
40 {
41   const char* ptr   = path;
42   const char* slash = NULL;
43   for(; *ptr != '\0'; ++ptr)
44   {
45     if(*ptr == '/') slash = ptr;
46   }
47   if(slash != NULL) ++slash;
48   return slash;
49 }
50
51 void SuppressLogOutput()
52 {
53   // Close stdout and stderr to suppress the log output
54   close(STDOUT_FILENO); // File descriptor number for stdout is 1
55   close(STDERR_FILENO); // File descriptor number for stderr is 2
56
57   // The POSIX specification requires that /dev/null must be provided,
58   // The open function always chooses the lowest unused file descriptor
59   // It is sufficient for stdout to be writable.
60   open("/dev/null", O_WRONLY); // Redirect file descriptor number 1 (i.e. stdout) to /dev/null
61   // When stderr is opened it must be both readable and writable.
62   open("/dev/null", O_RDWR); // Redirect file descriptor number 2 (i.e. stderr) to /dev/null
63 }
64
65 int32_t RunTestCase(struct ::testcase_s& testCase)
66 {
67   int32_t result = EXIT_STATUS_TESTCASE_FAILED;
68
69   // dont want to catch exception as we want to be able to get
70   // gdb stack trace from the first error
71   // by default tests should all always pass with no exceptions
72   if(testCase.startup)
73   {
74     testCase.startup();
75   }
76   try
77   {
78     result = testCase.function();
79   }
80   catch(const char*)
81   {
82     // just catch test fail exception, return is already set to EXIT_STATUS_TESTCASE_FAILED
83   }
84   if(testCase.cleanup)
85   {
86     testCase.cleanup();
87   }
88
89   return result;
90 }
91
92 int32_t RunTestCaseInChildProcess(struct ::testcase_s& testCase, bool suppressOutput)
93 {
94   int32_t testResult = EXIT_STATUS_TESTCASE_FAILED;
95
96   int32_t pid = fork();
97   if(pid == 0) // Child process
98   {
99     if(suppressOutput)
100     {
101       SuppressLogOutput();
102     }
103     else
104     {
105       printf("\n");
106       for(int32_t i = 0; i < 80; ++i) printf("#");
107       printf("\nTC: %s\n", testCase.name);
108       fflush(stdout);
109     }
110
111     int32_t status = RunTestCase(testCase);
112
113     if(!suppressOutput)
114     {
115       fflush(stdout);
116       fflush(stderr);
117       fclose(stdout);
118       fclose(stderr);
119     }
120     exit(status);
121   }
122   else if(pid == -1)
123   {
124     perror("fork");
125     exit(EXIT_STATUS_FORK_FAILED);
126   }
127   else // Parent process
128   {
129     int32_t status   = 0;
130     int32_t childPid = waitpid(pid, &status, 0);
131     if(childPid == -1)
132     {
133       perror("waitpid");
134       exit(EXIT_STATUS_WAITPID_FAILED);
135     }
136     if(WIFEXITED(status))
137     {
138       if(childPid > 0)
139       {
140         testResult = WEXITSTATUS(status);
141         if(testResult)
142         {
143           printf("Test case %s failed: %d\n", testCase.name, testResult);
144         }
145       }
146     }
147     else if(WIFSIGNALED(status))
148     {
149       int32_t signal = WTERMSIG(status);
150       testResult     = EXIT_STATUS_TESTCASE_ABORTED;
151       if(signal == SIGABRT)
152       {
153         printf("Test case %s failed: test case asserted\n", testCase.name);
154       }
155       else
156       {
157         printf("Test case %s failed: exit with signal %s\n", testCase.name, strsignal(WTERMSIG(status)));
158       }
159     }
160     else if(WIFSTOPPED(status))
161     {
162       printf("Test case %s failed: stopped with signal %s\n", testCase.name, strsignal(WSTOPSIG(status)));
163     }
164   }
165   fflush(stdout);
166   fflush(stderr);
167   return testResult;
168 }
169
170 void OutputStatistics(const char* processName, int32_t numPasses, int32_t numFailures)
171 {
172   FILE* fp = fopen("summary.xml", "a");
173   if(fp != NULL)
174   {
175     fprintf(fp,
176             "  <suite name=\"%s\">\n"
177             "    <total_case>%d</total_case>\n"
178             "    <pass_case>%d</pass_case>\n"
179             "    <pass_rate>%5.2f</pass_rate>\n"
180             "    <fail_case>%d</fail_case>\n"
181             "    <fail_rate>%5.2f</fail_rate>\n"
182             "    <block_case>0</block_case>\n"
183             "    <block_rate>0.00</block_rate>\n"
184             "    <na_case>0</na_case>\n"
185             "    <na_rate>0.00</na_rate>\n"
186             "  </suite>\n",
187             basename(processName),
188             numPasses + numFailures,
189             numPasses,
190             (float)numPasses / (numPasses + numFailures),
191             numFailures,
192             (float)numFailures / (numPasses + numFailures));
193     fclose(fp);
194   }
195 }
196
197 int32_t RunAll(const char* processName, ::testcase tc_array[])
198 {
199   int32_t numFailures = 0;
200   int32_t numPasses   = 0;
201
202   // Run test cases in child process( to kill output/handle signals ), but run serially.
203   for(uint32_t i = 0; tc_array[i].name; i++)
204   {
205     int32_t result = RunTestCaseInChildProcess(tc_array[i], false);
206     if(result == 0)
207     {
208       numPasses++;
209     }
210     else
211     {
212       numFailures++;
213     }
214   }
215
216   OutputStatistics(processName, numPasses, numFailures);
217
218   return numFailures;
219 }
220
221 // Constantly runs up to MAX_NUM_CHILDREN processes
222 int32_t RunAllInParallel(const char* processName, ::testcase tc_array[], bool reRunFailed)
223 {
224   int32_t numFailures = 0;
225   int32_t numPasses   = 0;
226
227   RunningTestCases     children;
228   std::vector<int32_t> failedTestCases;
229
230   // Fork up to MAX_NUM_CHILDREN processes, then
231   // wait. As soon as a proc completes, fork the next.
232
233   int32_t nextTestCase       = 0;
234   int32_t numRunningChildren = 0;
235
236   while(tc_array[nextTestCase].name || numRunningChildren > 0)
237   {
238     // Create more children (up to the max number or til the end of the array)
239     while(numRunningChildren < MAX_NUM_CHILDREN && tc_array[nextTestCase].name)
240     {
241       int32_t pid = fork();
242       if(pid == 0) // Child process
243       {
244         SuppressLogOutput();
245         exit(RunTestCase(tc_array[nextTestCase]));
246       }
247       else if(pid == -1)
248       {
249         perror("fork");
250         exit(EXIT_STATUS_FORK_FAILED);
251       }
252       else // Parent process
253       {
254         TestCase tc(nextTestCase, tc_array[nextTestCase].name);
255         tc.startTime = std::chrono::steady_clock::now();
256
257         children[pid] = tc;
258         nextTestCase++;
259         numRunningChildren++;
260       }
261     }
262
263     // Check to see if any children have finished yet
264     int32_t status   = 0;
265     int32_t childPid = waitpid(-1, &status, WNOHANG);
266     if(childPid == 0)
267     {
268       // No children have finished.
269       // Check if any have exceeded execution time
270       auto endTime = std::chrono::steady_clock::now();
271
272       for(auto& tc : children)
273       {
274         std::chrono::steady_clock::duration timeSpan = endTime - tc.second.startTime;
275         std::chrono::duration<double>       seconds  = std::chrono::duration_cast<std::chrono::duration<double>>(timeSpan);
276         if(seconds.count() > MAXIMUM_CHILD_LIFETIME)
277         {
278           // Kill the child process. A subsequent call to waitpid will process signal result below.
279           kill(tc.first, SIGKILL);
280         }
281       }
282     }
283     else if(childPid == -1) // waitpid errored
284     {
285       perror("waitpid");
286       exit(EXIT_STATUS_WAITPID_FAILED);
287     }
288     else // a child has finished
289     {
290       if(WIFEXITED(status))
291       {
292         int32_t testResult = WEXITSTATUS(status);
293         if(testResult)
294         {
295           printf("Test case %s failed: %d\n", children[childPid].testCaseName, testResult);
296           failedTestCases.push_back(children[childPid].testCase);
297           numFailures++;
298         }
299         else
300         {
301           numPasses++;
302         }
303         numRunningChildren--;
304       }
305
306       else if(WIFSIGNALED(status) || WIFSTOPPED(status))
307       {
308         status = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status);
309
310         RunningTestCases::iterator iter = children.find(childPid);
311         if(iter != children.end())
312         {
313           printf("Test case %s exited with signal %s\n", iter->second.testCaseName, strsignal(status));
314           failedTestCases.push_back(iter->second.testCase);
315         }
316         else
317         {
318           printf("Unknown child process: %d signaled %s\n", childPid, strsignal(status));
319         }
320
321         numFailures++;
322         numRunningChildren--;
323       }
324     }
325   }
326
327   OutputStatistics(processName, numPasses, numFailures);
328
329   if(reRunFailed)
330   {
331     for(uint32_t i = 0; i < failedTestCases.size(); i++)
332     {
333       char*   testCaseStrapline;
334       int32_t numChars = asprintf(&testCaseStrapline, "Test case %s", tc_array[failedTestCases[i]].name);
335       printf("\n%s\n", testCaseStrapline);
336       for(int32_t j = 0; j < numChars; j++)
337       {
338         printf("=");
339       }
340       printf("\n");
341       RunTestCaseInChildProcess(tc_array[failedTestCases[i]], false);
342     }
343   }
344
345   return numFailures;
346 }
347
348 int32_t FindAndRunTestCase(::testcase tc_array[], const char* testCaseName)
349 {
350   int32_t result = EXIT_STATUS_TESTCASE_NOT_FOUND;
351
352   for(int32_t i = 0; tc_array[i].name; i++)
353   {
354     if(!strcmp(testCaseName, tc_array[i].name))
355     {
356       return RunTestCase(tc_array[i]);
357     }
358   }
359
360   printf("Unknown testcase name: \"%s\"\n", testCaseName);
361   return result;
362 }
363
364 void Usage(const char* program)
365 {
366   printf(
367     "Usage: \n"
368     "   %s <testcase name>\t\t Execute a test case\n"
369     "   %s \t\t Execute all test cases in parallel\n"
370     "   %s -r\t\t Execute all test cases in parallel, rerunning failed test cases\n"
371     "   %s -s\t\t Execute all test cases serially\n",
372     program,
373     program,
374     program,
375     program);
376 }
377
378 } // namespace TestHarness