3 #include "../application/failure.h" 4 #include "../conversion/stringconversion.h" 5 #include "../io/catchiofailure.h" 10 #include <initializer_list> 31 TestApplication *TestApplication::m_instance =
nullptr;
43 TestApplication::TestApplication(
int argc,
char **argv)
45 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files")
46 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested")
47 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files")
48 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units")
52 throw runtime_error(
"only one TestApplication instance allowed at a time");
57 if (
const char *testFilesPathEnv = getenv(
"TEST_FILE_PATH")) {
58 if (
const auto len = strlen(testFilesPathEnv)) {
59 m_testFilesPathEnvValue.reserve(len + 1);
60 m_testFilesPathEnvValue += testFilesPathEnv;
61 m_testFilesPathEnvValue +=
'/';
66 for (
Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
67 arg->setRequiredValueCount(1);
68 arg->setValueNames({
"path" });
69 arg->setCombinable(
true);
76 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
85 cerr <<
"Directories used to search for testfiles:" << endl;
87 if (*m_testFilesPathArg.
values().front()) {
88 cerr << ((m_testFilesPathArgValue = m_testFilesPathArg.
values().front()) +=
'/') << endl;
90 cerr << (m_testFilesPathArgValue =
"./") << endl;
93 if (!m_testFilesPathEnvValue.empty()) {
94 cerr << m_testFilesPathEnvValue << endl;
96 cerr <<
"./testfiles/" << endl << endl;
97 cerr <<
"Directory used to store working copies:" << endl;
99 if (*m_workingDirArg.
values().front()) {
100 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
104 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
105 if (
const auto len = strlen(workingDirEnv)) {
106 m_workingDir.reserve(len + 1);
107 m_workingDir += workingDirEnv;
112 m_workingDir = m_testFilesPathArgValue +
"workingdir/";
113 }
else if (!m_testFilesPathEnvValue.empty()) {
114 m_workingDir = m_testFilesPathEnvValue +
"workingdir/";
116 m_workingDir =
"./testfiles/workingdir/";
119 cerr << m_workingDir << endl << endl;
122 cerr <<
"Executing test cases ..." << endl;
123 }
catch (
const Failure &failure) {
124 cerr <<
"Invalid arguments specified: " << failure.
what() << endl;
134 m_instance =
nullptr;
147 file.open(path = m_testFilesPathArgValue + name, ios_base::in);
154 if (!m_testFilesPathEnvValue.empty()) {
156 file.open(path = m_testFilesPathEnvValue + name, ios_base::in);
164 file.open(path =
"./testfiles/" + name, ios_base::in);
166 cerr <<
"Warning: The testfile \"" << path <<
"\" can not be located." << endl;
176 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 179 fstream origFile, workingCopy;
180 origFile.exceptions(ios_base::badbit | ios_base::failbit);
181 workingCopy.exceptions(ios_base::badbit | ios_base::failbit);
184 struct stat currentStat;
185 if (stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
186 if (mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
187 cerr <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << endl;
193 const auto parts = splitString<vector<string>>(name, string(
"/"), EmptyPartsTreat::Omit);
194 if (!parts.empty()) {
195 string currentLevel = m_workingDir;
196 for (
auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
197 if (currentLevel.back() !=
'/') {
201 if (stat(currentLevel.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
202 if (mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
203 cerr <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << endl;
213 origFile.open(
testFilePath(name), ios_base::in | ios_base::binary);
214 const string path = m_workingDir + name;
215 workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc);
216 workingCopy << origFile.rdbuf();
220 cerr <<
"Unable to create working copy for \"" << name <<
"\": an IO error occured." << endl;
223 return m_workingDir + name;
228 string TestApplication::workingCopyPath(
const string &name)
const 242 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 244 return execHelperApp(m_applicationPathArg.
firstValue(), args, output, errors, suppressLogging, timeout);
247 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
250 if (!suppressLogging) {
252 for (
const char *
const *i = args; *i; ++i) {
258 if (!appPath || !*appPath) {
259 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
262 int coutPipes[2], cerrPipes[2];
263 pipe(coutPipes), pipe(cerrPipes);
264 int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
265 int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
267 if (
int child = fork()) {
269 close(writeCoutPipe), close(writeCerrPipe);
273 throw runtime_error(
"Unable to create fork");
277 struct pollfd fileDescriptorSet[2];
278 fileDescriptorSet[0].fd = readCoutPipe;
279 fileDescriptorSet[1].fd = readCerrPipe;
280 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
285 output.clear(), errors.clear();
289 int retpoll = poll(fileDescriptorSet, 2, timeout);
292 if (fileDescriptorSet[0].revents & POLLIN) {
293 if ((count = read(readCoutPipe, buffer,
sizeof(buffer))) > 0) {
294 output.append(buffer, count);
296 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
298 fileDescriptorSet[0].fd = -1;
300 if (fileDescriptorSet[1].revents & POLLIN) {
301 if ((count = read(readCerrPipe, buffer,
sizeof(buffer))) > 0) {
302 errors.append(buffer, count);
304 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
306 fileDescriptorSet[1].fd = -1;
308 }
else if (retpoll == 0) {
310 throw runtime_error(
"Poll time-out");
313 throw runtime_error(
"Poll failed");
315 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
318 close(readCoutPipe), close(readCerrPipe);
324 waitpid(child, &childReturnCode, 0);
325 return childReturnCode;
328 dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
329 close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
330 execv(appPath, const_cast<char *const *>(args));
331 cerr <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << endl;
void setCombinable(bool value)
Sets whether this argument can be combined.
virtual const char * what() const USE_NOTHROW
Returns a C-style character string describing the cause of the Failure.
Contains currently only ArgumentParser and related classes.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
std::string testFilePath(const std::string &name) const
Returns the full path of the test file with the specified name.
void parseArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
Contains utility classes helping to read and write streams.
Contains classes and functions utilizing creating of test applications.
~TestApplication()
Destroys the TestApplication.
const std::vector< const char * > & values(std::size_t occurrence=0) const
Returns the parameter values for the specified occurrence of argument.
Contains several functions providing conversions between different data types.
The Argument class is a wrapper for command line argument information.
WorkingCopyMode
The WorkingCopyMode enum specifies additional options to influence behavior of TestApplication::worki...
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
The Failure class is thrown by an ArgumentParser when a parsing error occurs.
CPP_UTILITIES_EXPORT const char * catchIoFailure()
Provides a workaround for GCC Bug 66145.
void setRequiredValueCount(std::size_t requiredValueCount)
Sets the number of values which are required to be given for this argument.
void setValueNames(std::initializer_list< const char *> valueNames)
Sets the names of the requried values.