/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*/
#include "test-harness.h"
+
+#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <testcase.h>
#include <unistd.h>
-#include <vector>
-#include <map>
+
#include <cstring>
-#include <testcase.h>
+#include <map>
+#include <vector>
namespace TestHarness
{
+typedef std::map<int32_t, TestCase> RunningTestCases;
-typedef std::map<int, TestCase> RunningTestCases;
-
-namespace
+const char* basename(const char* path)
{
-const char* RED_COLOR="\e[1;31m";
-const char* GREEN_COLOR="\e[1;32m";
-const char* ASCII_RESET="\e[0m";
-const char* ASCII_BOLD="\e[1m";
+ const char* ptr = path;
+ const char* slash = NULL;
+ for(; *ptr != '\0'; ++ptr)
+ {
+ if(*ptr == '/') slash = ptr;
+ }
+ if(slash != NULL) ++slash;
+ return slash;
}
+void SuppressLogOutput()
+{
+ // Close stdout and stderr to suppress the log output
+ close(STDOUT_FILENO); // File descriptor number for stdout is 1
+ close(STDERR_FILENO); // File descriptor number for stderr is 2
+
+ // The POSIX specification requires that /dev/null must be provided,
+ // The open function always chooses the lowest unused file descriptor
+ // It is sufficient for stdout to be writable.
+ open("/dev/null", O_WRONLY); // Redirect file descriptor number 1 (i.e. stdout) to /dev/null
+ // When stderr is opened it must be both readable and writable.
+ open("/dev/null", O_RDWR); // Redirect file descriptor number 2 (i.e. stderr) to /dev/null
+}
-int RunTestCase( struct ::testcase_s& testCase )
+int32_t RunTestCase(struct ::testcase_s& testCase)
{
- int result = EXIT_STATUS_TESTCASE_FAILED;
+ int32_t result = EXIT_STATUS_TESTCASE_FAILED;
-// dont want to catch exception as we want to be able to get
-// gdb stack trace from the first error
-// by default tests should all always pass with no exceptions
- if( testCase.startup )
+ // dont want to catch exception as we want to be able to get
+ // gdb stack trace from the first error
+ // by default tests should all always pass with no exceptions
+ if(testCase.startup)
{
testCase.startup();
}
- result = testCase.function();
- if( testCase.cleanup )
+ try
+ {
+ result = testCase.function();
+ }
+ catch(const char*)
+ {
+ // just catch test fail exception, return is already set to EXIT_STATUS_TESTCASE_FAILED
+ }
+ if(testCase.cleanup)
{
testCase.cleanup();
}
return result;
}
-
-int RunTestCaseInChildProcess( struct ::testcase_s& testCase, bool suppressOutput )
+int32_t RunTestCaseInChildProcess(struct ::testcase_s& testCase, bool suppressOutput)
{
- int testResult = EXIT_STATUS_TESTCASE_FAILED;
+ int32_t testResult = EXIT_STATUS_TESTCASE_FAILED;
- int pid = fork();
- if( pid == 0 ) // Child process
+ int32_t pid = fork();
+ if(pid == 0) // Child process
{
- if( suppressOutput )
+ if(suppressOutput)
{
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
+ SuppressLogOutput();
}
- exit( RunTestCase( testCase ) );
+ else
+ {
+ printf("\n");
+ for(int32_t i = 0; i < 80; ++i) printf("#");
+ printf("\nTC: %s\n", testCase.name);
+ fflush(stdout);
+ }
+
+ int32_t status = RunTestCase(testCase);
+
+ if(!suppressOutput)
+ {
+ fflush(stdout);
+ fflush(stderr);
+ fclose(stdout);
+ fclose(stderr);
+ }
+ exit(status);
}
else if(pid == -1)
{
}
else // Parent process
{
- int status = 0;
- int childPid = waitpid(-1, &status, 0);
- if( childPid == -1 )
+ int32_t status = 0;
+ int32_t childPid = waitpid(pid, &status, 0);
+ if(childPid == -1)
{
perror("waitpid");
exit(EXIT_STATUS_WAITPID_FAILED);
}
- if( WIFEXITED(status) )
+ if(WIFEXITED(status))
{
- if( childPid > 0 )
+ if(childPid > 0)
{
testResult = WEXITSTATUS(status);
- if( testResult )
+ if(testResult)
{
printf("Test case %s failed: %d\n", testCase.name, testResult);
}
}
}
- else if(WIFSIGNALED(status) )
+ else if(WIFSIGNALED(status))
{
- testResult = EXIT_STATUS_TESTCASE_ABORTED;
-
-#ifdef WCOREDUMP
- if(WCOREDUMP(status))
+ int32_t signal = WTERMSIG(status);
+ testResult = EXIT_STATUS_TESTCASE_ABORTED;
+ if(signal == SIGABRT)
{
- printf("Test case %s failed: due to a crash\n", testCase.name);
+ printf("Test case %s failed: test case asserted\n", testCase.name);
+ }
+ else
+ {
+ printf("Test case %s failed: exit with signal %s\n", testCase.name, strsignal(WTERMSIG(status)));
}
-#endif
- printf("Test case %s failed: exit with signal %s\n", testCase.name, strsignal(WTERMSIG(status)));
}
else if(WIFSTOPPED(status))
{
printf("Test case %s failed: stopped with signal %s\n", testCase.name, strsignal(WSTOPSIG(status)));
}
}
+ fflush(stdout);
+ fflush(stderr);
return testResult;
}
-void OutputStatistics( int numPasses, int numFailures )
+void OutputStatistics(const char* processName, int32_t numPasses, int32_t numFailures)
{
- const char* failureColor = GREEN_COLOR;
- if( numFailures > 0 )
+ FILE* fp = fopen("summary.xml", "a");
+ if(fp != NULL)
{
- failureColor = RED_COLOR;
+ fprintf(fp,
+ " <suite name=\"%s\">\n"
+ " <total_case>%d</total_case>\n"
+ " <pass_case>%d</pass_case>\n"
+ " <pass_rate>%5.2f</pass_rate>\n"
+ " <fail_case>%d</fail_case>\n"
+ " <fail_rate>%5.2f</fail_rate>\n"
+ " <block_case>0</block_case>\n"
+ " <block_rate>0.00</block_rate>\n"
+ " <na_case>0</na_case>\n"
+ " <na_rate>0.00</na_rate>\n"
+ " </suite>\n",
+ basename(processName),
+ numPasses + numFailures,
+ numPasses,
+ (float)numPasses / (numPasses + numFailures),
+ numFailures,
+ (float)numFailures / (numPasses + numFailures));
+ fclose(fp);
}
- printf("\rNumber of test passes: %s%4d (%5.2f%%)%s\n", ASCII_BOLD, numPasses, 100.0f * (float)numPasses / (numPasses+numFailures), ASCII_RESET);
- printf("%sNumber of test failures:%s %s%4d%s\n", failureColor, ASCII_RESET, ASCII_BOLD, numFailures, ASCII_RESET);
-
}
-
-int RunAll(const char* processName, ::testcase tc_array[], bool reRunFailed)
+int32_t RunAll(const char* processName, ::testcase tc_array[])
{
- int numFailures = 0;
- int numPasses = 0;
+ int32_t numFailures = 0;
+ int32_t numPasses = 0;
// Run test cases in child process( to kill output/handle signals ), but run serially.
- for( unsigned int i=0; tc_array[i].name; i++)
+ for(uint32_t i = 0; tc_array[i].name; i++)
{
- int result = RunTestCaseInChildProcess( tc_array[i], true );
- if( result == 0 )
+ int32_t result = RunTestCaseInChildProcess(tc_array[i], false);
+ if(result == 0)
{
numPasses++;
}
}
}
- OutputStatistics(numPasses, numFailures);
+ OutputStatistics(processName, numPasses, numFailures);
return numFailures;
}
-
-
// Constantly runs up to MAX_NUM_CHILDREN processes
-int RunAllInParallel( const char* processName, ::testcase tc_array[], bool reRunFailed)
+int32_t RunAllInParallel(const char* processName, ::testcase tc_array[], bool reRunFailed)
{
- int numFailures = 0;
- int numPasses = 0;
+ int32_t numFailures = 0;
+ int32_t numPasses = 0;
- RunningTestCases children;
- std::vector<int> failedTestCases;
+ RunningTestCases children;
+ std::vector<int32_t> failedTestCases;
// Fork up to MAX_NUM_CHILDREN processes, then
// wait. As soon as a proc completes, fork the next.
- int nextTestCase = 0;
- int numRunningChildren = 0;
+ int32_t nextTestCase = 0;
+ int32_t numRunningChildren = 0;
- while( tc_array[nextTestCase].name || numRunningChildren > 0)
+ while(tc_array[nextTestCase].name || numRunningChildren > 0)
{
// Create more children (up to the max number or til the end of the array)
- while( numRunningChildren < MAX_NUM_CHILDREN && tc_array[nextTestCase].name )
+ while(numRunningChildren < MAX_NUM_CHILDREN && tc_array[nextTestCase].name)
{
- int pid = fork();
- if( pid == 0 ) // Child process
+ int32_t pid = fork();
+ if(pid == 0) // Child process
{
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
- exit( RunTestCase( tc_array[nextTestCase] ) );
+ SuppressLogOutput();
+ exit(RunTestCase(tc_array[nextTestCase]));
}
else if(pid == -1)
{
// Wait for the next child to finish
- int status=0;
- int childPid = waitpid(-1, &status, 0);
- if( childPid == -1 )
+ int32_t status = 0;
+ int32_t childPid = waitpid(-1, &status, 0);
+ if(childPid == -1)
{
perror("waitpid");
exit(EXIT_STATUS_WAITPID_FAILED);
}
- if( WIFEXITED(status) )
+ if(WIFEXITED(status))
{
- if( childPid > 0 )
+ if(childPid > 0)
{
- int testResult = WEXITSTATUS(status);
- if( testResult )
+ int32_t testResult = WEXITSTATUS(status);
+ if(testResult)
{
printf("Test case %s failed: %d\n", children[childPid].testCaseName, testResult);
failedTestCases.push_back(children[childPid].testCase);
}
}
- else if( WIFSIGNALED(status) || WIFSTOPPED(status))
+ else if(WIFSIGNALED(status) || WIFSTOPPED(status))
{
- status = WIFSIGNALED(status)?WTERMSIG(status):WSTOPSIG(status);
+ status = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status);
- if( childPid > 0 )
+ if(childPid > 0)
{
RunningTestCases::iterator iter = children.find(childPid);
- if( iter != children.end() )
+ if(iter != children.end())
{
printf("Test case %s exited with signal %s\n", iter->second.testCaseName, strsignal(status));
failedTestCases.push_back(iter->second.testCase);
}
}
- OutputStatistics( numPasses, numFailures );
+ OutputStatistics(processName, numPasses, numFailures);
- if( reRunFailed )
+ if(reRunFailed)
{
- for( unsigned int i=0; i<failedTestCases.size(); i++)
+ for(uint32_t i = 0; i < failedTestCases.size(); i++)
{
- char* testCaseStrapline;
- int numChars = asprintf(&testCaseStrapline, "Test case %s", tc_array[failedTestCases[i]].name );
+ char* testCaseStrapline;
+ int32_t numChars = asprintf(&testCaseStrapline, "Test case %s", tc_array[failedTestCases[i]].name);
printf("\n%s\n", testCaseStrapline);
- for(int j=0; j<numChars; j++)
+ for(int32_t j = 0; j < numChars; j++)
{
printf("=");
}
printf("\n");
- RunTestCaseInChildProcess( tc_array[failedTestCases[i] ], false );
+ RunTestCaseInChildProcess(tc_array[failedTestCases[i]], false);
}
}
return numFailures;
}
-
-
-int FindAndRunTestCase(::testcase tc_array[], const char* testCaseName)
+int32_t FindAndRunTestCase(::testcase tc_array[], const char* testCaseName)
{
- int result = EXIT_STATUS_TESTCASE_NOT_FOUND;
+ int32_t result = EXIT_STATUS_TESTCASE_NOT_FOUND;
- for( int i = 0; tc_array[i].name; i++ )
+ for(int32_t i = 0; tc_array[i].name; i++)
{
- if( !strcmp(testCaseName, tc_array[i].name) )
+ if(!strcmp(testCaseName, tc_array[i].name))
{
- return RunTestCase( tc_array[i] );
+ return RunTestCase(tc_array[i]);
}
}
void Usage(const char* program)
{
- printf("Usage: \n"
- " %s <testcase name>\t\t Execute a test case\n"
- " %s \t\t Execute all test cases in parallel\n"
- " %s -r\t\t Execute all test cases in parallel, rerunning failed test cases\n",
- program, program, program);
+ printf(
+ "Usage: \n"
+ " %s <testcase name>\t\t Execute a test case\n"
+ " %s \t\t Execute all test cases in parallel\n"
+ " %s -r\t\t Execute all test cases in parallel, rerunning failed test cases\n"
+ " %s -s\t\t Execute all test cases serially\n",
+ program,
+ program,
+ program,
+ program);
}
-} // namespace
+} // namespace TestHarness