3 #include "../application/failure.h" 4 #include "../conversion/stringbuilder.h" 5 #include "../conversion/stringconversion.h" 6 #include "../io/ansiescapecodes.h" 7 #include "../io/catchiofailure.h" 8 #include "../io/misc.h" 9 #include "../io/path.h" 14 #include <initializer_list> 36 TestApplication *TestApplication::m_instance =
nullptr;
48 TestApplication::TestApplication(
int argc,
char **argv)
50 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files")
51 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested")
52 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files")
53 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units")
57 throw runtime_error(
"only one TestApplication instance allowed at a time");
63 m_fallbackTestFilesPath = readTestfilePathFromEnv();
65 bool fallbackIsSourceDir = m_fallbackTestFilesPath.empty();
66 if (fallbackIsSourceDir) {
67 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
73 for (
Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
74 arg->setRequiredValueCount(1);
75 arg->setValueNames({
"path" });
76 arg->setCombinable(
true);
81 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
86 }
catch (
const Failure &failure) {
99 cerr <<
"Directories used to search for testfiles:" << endl;
101 if (*m_testFilesPathArg.
values().front()) {
102 cerr << ((m_testFilesPath = m_testFilesPathArg.
values().front()) +=
'/') << endl;
104 cerr << (m_testFilesPath =
"./") << endl;
108 m_testFilesPath.swap(m_fallbackTestFilesPath);
111 if (m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
112 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
113 fallbackIsSourceDir =
true;
115 if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
116 cerr << m_fallbackTestFilesPath << endl;
118 cerr <<
"./testfiles/" << endl << endl;
119 cerr <<
"Directory used to store working copies:" << endl;
121 if (*m_workingDirArg.
values().front()) {
122 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
126 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
127 if (*workingDirEnv) {
132 m_workingDir = m_testFilesPath +
"workingdir/";
133 }
else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
134 m_workingDir = m_fallbackTestFilesPath +
"workingdir/";
136 m_workingDir =
"./testfiles/workingdir/";
139 cerr << m_workingDir << endl << endl;
142 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
143 ofstream(profrawListFile, ios_base::trunc);
147 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
155 m_instance =
nullptr;
176 if (!m_testFilesPath.empty()) {
177 file.open(path = m_testFilesPath + name, ios_base::in);
184 if (!m_fallbackTestFilesPath.empty()) {
186 file.open(path = m_fallbackTestFilesPath + name, ios_base::in);
194 file.open(path =
"./testfiles/" + name, ios_base::in);
196 cerr << Phrases::Warning <<
"The testfile \"" << path <<
"\" can not be located." << Phrases::EndFlush;
210 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 213 fstream origFile, workingCopy;
214 origFile.exceptions(ios_base::badbit | ios_base::failbit);
215 workingCopy.exceptions(ios_base::badbit | ios_base::failbit);
218 struct stat currentStat;
219 if (stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
220 if (mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
221 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << Phrases::EndFlush;
227 const auto parts = splitString<vector<string>>(name, string(
"/"), EmptyPartsTreat::Omit);
228 if (!parts.empty()) {
229 string currentLevel = m_workingDir;
230 for (
auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
231 if (currentLevel.back() !=
'/') {
235 if (stat(currentLevel.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
236 if (mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
237 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." 238 << Phrases::EndFlush;
248 origFile.open(
testFilePath(name), ios_base::in | ios_base::binary);
249 const string path = m_workingDir + name;
250 workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc);
251 workingCopy << origFile.rdbuf();
255 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occured." << Phrases::EndFlush;
258 return m_workingDir + name;
270 string TestApplication::workingCopyPath(
const string &name)
const 279 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
280 const std::string &newProfilingPath)
283 if (!suppressLogging) {
285 for (
const char *
const *i = args; *i; ++i) {
292 int coutPipes[2], cerrPipes[2];
293 pipe(coutPipes), pipe(cerrPipes);
294 int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
295 int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
298 if (
int child = fork()) {
300 close(writeCoutPipe), close(writeCerrPipe);
304 throw runtime_error(
"Unable to create fork");
308 struct pollfd fileDescriptorSet[2];
309 fileDescriptorSet[0].fd = readCoutPipe;
310 fileDescriptorSet[1].fd = readCerrPipe;
311 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
316 output.clear(), errors.clear();
320 int retpoll = poll(fileDescriptorSet, 2, timeout);
323 if (fileDescriptorSet[0].revents & POLLIN) {
324 if ((count = read(readCoutPipe, buffer,
sizeof(buffer))) > 0) {
325 output.append(buffer, static_cast<size_t>(count));
327 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
329 fileDescriptorSet[0].fd = -1;
331 if (fileDescriptorSet[1].revents & POLLIN) {
332 if ((count = read(readCerrPipe, buffer,
sizeof(buffer))) > 0) {
333 errors.append(buffer, static_cast<size_t>(count));
335 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
337 fileDescriptorSet[1].fd = -1;
339 }
else if (retpoll == 0) {
341 throw runtime_error(
"Poll time-out");
344 throw runtime_error(
"Poll failed");
346 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
349 close(readCoutPipe), close(readCerrPipe);
355 waitpid(child, &childReturnCode, 0);
356 return childReturnCode;
360 dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
361 close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
364 if (!newProfilingPath.empty()) {
365 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
369 execv(appPath, const_cast<char *const *>(args));
370 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
384 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 387 static unsigned int invocationCount = 0;
392 string fallbackAppPath;
397 const char *
const testAppPath = m_parser.
executable();
398 const size_t testAppPathLength = strlen(testAppPath);
399 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
400 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
401 appPath = fallbackAppPath.data();
404 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
409 string newProfilingPath;
410 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
412 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
413 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
415 const char *appName = strrchr(
appPath,
'/');
416 appName = appName ? appName + 1 :
appPath;
418 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
420 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
421 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
426 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
436 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
438 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
440 #endif // PLATFORM_UNIX 442 string TestApplication::readTestfilePathFromEnv()
444 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
445 if (!testFilesPathEnv || !*testFilesPathEnv) {
451 string TestApplication::readTestfilePathFromSrcRef()
456 string srcDirContent(
readFile(
"srcdirref", 2 * 1024));
457 if (srcDirContent.empty()) {
458 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
463 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 464 bool hasTestfilesDir =
false;
465 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
466 if (dir ==
"testfiles") {
467 hasTestfilesDir =
true;
471 if (!hasTestfilesDir) {
472 cerr << Phrases::Warning
473 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 474 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
477 #endif // PLATFORM_UNIX 479 return srcDirContent +=
"/testfiles/";
Encapsulates functions for formatted terminal output using ANSI escape codes.
void setCombinable(bool value)
Sets whether this argument can be combined.
CPP_UTILITIES_EXPORT std::list< std::string > directoryEntries(const char *path, DirectoryEntryType types=DirectoryEntryType::All)
Returns the names of the directory entries in the specified path with the specified types...
Contains currently only ArgumentParser and related classes.
constexpr StringType argsToString(Args &&... args)
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.
CPP_UTILITIES_EXPORT std::string readFile(const std::string &path, std::string::size_type maxSize=std::string::npos)
Reads all contents of the specified file in a single call.
static const char * appPath()
Returns the application path or an empty string if no application path has been set.
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.
const char * executable() const
Returns the name of the current executable.
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.