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);
74 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
83 cerr <<
"Directories used to search for testfiles:" << endl;
85 if (*m_testFilesPathArg.
values().front()) {
86 cerr << ((m_testFilesPathArgValue = m_testFilesPathArg.
values().front()) +=
'/') << endl;
88 cerr << (m_testFilesPathArgValue =
"./") << endl;
91 if (!m_testFilesPathEnvValue.empty()) {
92 cerr << m_testFilesPathEnvValue << endl;
94 cerr <<
"./testfiles/" << endl << endl;
95 cerr <<
"Directory used to store working copies:" << endl;
97 if (*m_workingDirArg.
values().front()) {
98 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
102 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
103 if (
const auto len = strlen(workingDirEnv)) {
104 m_workingDir.reserve(len + 1);
105 m_workingDir += workingDirEnv;
110 m_workingDir = m_testFilesPathArgValue +
"workingdir/";
111 }
else if (!m_testFilesPathEnvValue.empty()) {
112 m_workingDir = m_testFilesPathEnvValue +
"workingdir/";
114 m_workingDir =
"./testfiles/workingdir/";
117 cerr << m_workingDir << endl << endl;
120 cerr <<
"Executing test cases ..." << endl;
121 }
catch (
const Failure &failure) {
122 cerr <<
"Invalid arguments specified: " << failure.
what() << endl;
132 m_instance =
nullptr;
145 file.open(path = m_testFilesPathArgValue + name, ios_base::in);
152 if (!m_testFilesPathEnvValue.empty()) {
154 file.open(path = m_testFilesPathEnvValue + name, ios_base::in);
162 file.open(path =
"./testfiles/" + name, ios_base::in);
164 cerr <<
"Warning: The testfile \"" << path <<
"\" can not be located." << endl;
174 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 177 fstream origFile, workingCopy;
178 origFile.exceptions(ios_base::badbit | ios_base::failbit);
179 workingCopy.exceptions(ios_base::badbit | ios_base::failbit);
182 struct stat currentStat;
183 if (stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
184 if (mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
185 cerr <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << endl;
191 const auto parts = splitString<vector<string>>(name, string(
"/"), EmptyPartsTreat::Omit);
192 if (!parts.empty()) {
193 string currentLevel = m_workingDir;
194 for (
auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
195 if (currentLevel.back() !=
'/') {
199 if (stat(currentLevel.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
200 if (mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
201 cerr <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << endl;
211 origFile.open(
testFilePath(name), ios_base::in | ios_base::binary);
212 const string path = m_workingDir + name;
213 workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc);
214 workingCopy << origFile.rdbuf();
218 cerr <<
"Unable to create working copy for \"" << name <<
"\": an IO error occured." << endl;
221 return m_workingDir + name;
226 string TestApplication::workingCopyPath(
const string &name)
const 240 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 242 return execHelperApp(m_applicationPathArg.
firstValue(), args, output, errors, suppressLogging, timeout);
245 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
248 if (!suppressLogging) {
250 for (
const char *
const *i = args; *i; ++i) {
256 if (!appPath || !*appPath) {
257 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
260 int coutPipes[2], cerrPipes[2];
261 pipe(coutPipes), pipe(cerrPipes);
262 int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
263 int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
265 if (
int child = fork()) {
267 close(writeCoutPipe), close(writeCerrPipe);
271 throw runtime_error(
"Unable to create fork");
275 struct pollfd fileDescriptorSet[2];
276 fileDescriptorSet[0].fd = readCoutPipe;
277 fileDescriptorSet[1].fd = readCerrPipe;
278 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
283 output.clear(), errors.clear();
287 int retpoll = poll(fileDescriptorSet, 2, timeout);
290 if (fileDescriptorSet[0].revents & POLLIN) {
291 if ((count = read(readCoutPipe, buffer,
sizeof(buffer))) > 0) {
292 output.append(buffer, count);
294 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
296 fileDescriptorSet[0].fd = -1;
298 if (fileDescriptorSet[1].revents & POLLIN) {
299 if ((count = read(readCerrPipe, buffer,
sizeof(buffer))) > 0) {
300 errors.append(buffer, count);
302 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
304 fileDescriptorSet[1].fd = -1;
306 }
else if (retpoll == 0) {
308 throw runtime_error(
"Poll time-out");
311 throw runtime_error(
"Poll failed");
313 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
316 close(readCoutPipe), close(readCerrPipe);
322 waitpid(child, &childReturnCode, 0);
323 return childReturnCode;
326 dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
327 close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
328 execv(appPath, const_cast<char *const *>(args));
329 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.