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" 15 #include <initializer_list> 37 TestApplication *TestApplication::m_instance =
nullptr;
49 TestApplication::TestApplication(
int argc,
char **argv)
51 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files")
52 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested")
53 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files")
54 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units")
58 throw runtime_error(
"only one TestApplication instance allowed at a time");
64 m_fallbackTestFilesPath = readTestfilePathFromEnv();
66 bool fallbackIsSourceDir = m_fallbackTestFilesPath.empty();
67 if (fallbackIsSourceDir) {
68 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
74 for (
Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
75 arg->setRequiredValueCount(1);
76 arg->setValueNames({
"path" });
77 arg->setCombinable(
true);
82 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
87 }
catch (
const Failure &failure) {
100 cerr <<
"Directories used to search for testfiles:" << endl;
102 if (*m_testFilesPathArg.
values().front()) {
103 cerr << ((m_testFilesPath = m_testFilesPathArg.
values().front()) +=
'/') << endl;
105 cerr << (m_testFilesPath =
"./") << endl;
109 m_testFilesPath.swap(m_fallbackTestFilesPath);
112 if (m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
113 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
114 fallbackIsSourceDir =
true;
116 if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
117 cerr << m_fallbackTestFilesPath << endl;
119 cerr <<
"./testfiles/" << endl << endl;
120 cerr <<
"Directory used to store working copies:" << endl;
122 if (*m_workingDirArg.
values().front()) {
123 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
127 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
128 if (*workingDirEnv) {
133 m_workingDir = m_testFilesPath +
"workingdir/";
134 }
else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
135 m_workingDir = m_fallbackTestFilesPath +
"workingdir/";
137 m_workingDir =
"./testfiles/workingdir/";
140 cerr << m_workingDir << endl << endl;
143 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
144 ofstream(profrawListFile, ios_base::trunc);
148 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
156 m_instance =
nullptr;
177 if (!m_testFilesPath.empty()) {
178 file.open(path = m_testFilesPath + name, ios_base::in);
185 if (!m_fallbackTestFilesPath.empty()) {
187 file.open(path = m_fallbackTestFilesPath + name, ios_base::in);
195 file.open(path =
"./testfiles/" + name, ios_base::in);
197 cerr << Phrases::Warning <<
"The testfile \"" << path <<
"\" can not be located." << Phrases::EndFlush;
211 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 214 struct stat currentStat;
215 if ((stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode))
216 && mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
217 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << Phrases::EndFlush;
222 const auto parts = splitString<vector<string>>(name,
"/", EmptyPartsTreat::Omit);
223 if (!parts.empty()) {
226 currentLevel.reserve(m_workingDir.size() + name.size() + 1);
227 currentLevel.assign(m_workingDir);
228 for (
auto i = parts.cbegin(), end = parts.end() - 1;
i != end; ++
i) {
229 if (currentLevel.back() !=
'/') {
235 if (!stat(currentLevel.c_str(), ¤tStat) && S_ISDIR(currentStat.st_mode)) {
239 if (!mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
243 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << Phrases::EndFlush;
250 return m_workingDir + name;
255 auto workingCopyPath(m_workingDir + name);
256 size_t workingCopyPathAttempt = 0;
257 fstream origFile, workingCopy;
258 origFile.open(origFilePath, ios_base::in | ios_base::binary);
259 if (origFile.fail()) {
260 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occurred when opening original file \"" 261 << origFilePath <<
"\"." << Phrases::EndFlush;
262 cerr <<
"error: " << strerror(errno) << endl;
265 workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
266 while (workingCopy.fail() && !stat(workingCopyPath.c_str(), ¤tStat)) {
268 workingCopyPath =
argsToString(m_workingDir, name,
'.', ++workingCopyPathAttempt);
270 workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
272 if (workingCopy.fail()) {
273 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occurred when opening target file \"" 274 << workingCopyPath <<
"\"." << Phrases::EndFlush;
275 cerr <<
"error: " << strerror(errno) << endl;
278 workingCopy << origFile.rdbuf();
279 if (!origFile.fail() && !workingCopy.fail()) {
280 return workingCopyPath;
283 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": ";
284 if (origFile.fail()) {
285 cerr <<
"an IO error occurred when reading original file \"" << origFilePath <<
"\"";
288 if (workingCopy.fail()) {
289 if (origFile.fail()) {
292 cerr <<
" an IO error occurred when writing to target file \"" << workingCopyPath <<
"\".";
294 cerr <<
"error: " << strerror(errno) << endl;
305 string TestApplication::workingCopyPath(
const string &name)
const 314 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
315 const std::string &newProfilingPath)
318 if (!suppressLogging) {
320 for (
const char *
const *
i = args; *
i; ++
i) {
327 int coutPipes[2], cerrPipes[2];
330 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
331 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
334 if (
const auto child = fork()) {
336 close(writeCoutPipe);
337 close(writeCerrPipe);
341 throw runtime_error(
"Unable to create fork");
345 struct pollfd fileDescriptorSet[2];
346 fileDescriptorSet[0].fd = readCoutPipe;
347 fileDescriptorSet[1].fd = readCerrPipe;
348 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
357 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
359 throw runtime_error(
"Poll time-out");
362 throw runtime_error(
"Poll failed");
364 if (fileDescriptorSet[0].revents & POLLIN) {
365 const auto count = read(readCoutPipe, buffer,
sizeof(buffer));
367 output.append(buffer, static_cast<size_t>(count));
369 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
371 fileDescriptorSet[0].fd = -1;
373 if (fileDescriptorSet[1].revents & POLLIN) {
374 const auto count = read(readCerrPipe, buffer,
sizeof(buffer));
376 errors.append(buffer, static_cast<size_t>(count));
378 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
380 fileDescriptorSet[1].fd = -1;
382 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
392 waitpid(child, &childReturnCode, 0);
393 return childReturnCode;
397 dup2(writeCoutPipe, STDOUT_FILENO);
398 dup2(writeCerrPipe, STDERR_FILENO);
400 close(writeCoutPipe);
402 close(writeCerrPipe);
405 if (!newProfilingPath.empty()) {
406 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
410 execv(appPath, const_cast<char *const *>(args));
411 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
425 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 428 static unsigned int invocationCount = 0;
433 string fallbackAppPath;
438 const char *
const testAppPath = m_parser.
executable();
439 const size_t testAppPathLength = strlen(testAppPath);
440 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
441 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
442 appPath = fallbackAppPath.data();
445 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
450 string newProfilingPath;
451 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
453 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
454 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
456 const char *appName = strrchr(
appPath,
'/');
457 appName = appName ? appName + 1 :
appPath;
459 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
461 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
462 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
467 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
477 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
479 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
481 #endif // PLATFORM_UNIX 483 string TestApplication::readTestfilePathFromEnv()
485 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
486 if (!testFilesPathEnv || !*testFilesPathEnv) {
492 string TestApplication::readTestfilePathFromSrcRef()
497 auto srcDirContent(
readFile(
"srcdirref", 2 * 1024));
498 if (srcDirContent.empty()) {
499 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
504 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 505 bool hasTestfilesDir =
false;
506 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
507 if (dir ==
"testfiles") {
508 hasTestfilesDir =
true;
512 if (!hasTestfilesDir) {
513 cerr << Phrases::Warning
514 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 515 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
518 #endif // PLATFORM_UNIX 520 return srcDirContent +=
"/testfiles/";
522 cerr << Phrases::Warning <<
"The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory." 523 << Phrases::EndFlush;
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.