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/nativefilestream.h" 10 #include "../io/path.h" 16 #include <initializer_list> 27 #ifdef PLATFORM_WINDOWS 46 return stat(path.data(), &res) == 0;
48 const auto widePath(convertMultiByteToWide(path));
49 if (!widePath.first) {
52 const auto fileType(GetFileAttributesW(widePath.first.get()));
53 return fileType != INVALID_FILE_ATTRIBUTES;
61 return stat(path.data(), &res) == 0 && !S_ISDIR(res.st_mode);
63 const auto widePath(convertMultiByteToWide(path));
64 if (!widePath.first) {
67 const auto fileType(GetFileAttributesW(widePath.first.get()));
68 return (fileType != INVALID_FILE_ATTRIBUTES) && !(fileType & FILE_ATTRIBUTE_DIRECTORY) && !(fileType & FILE_ATTRIBUTE_DEVICE);
76 return stat(path.data(), &res) == 0 && S_ISDIR(res.st_mode);
78 const auto widePath(convertMultiByteToWide(path));
79 if (!widePath.first) {
82 const auto fileType(GetFileAttributesW(widePath.first.get()));
83 return (fileType != INVALID_FILE_ATTRIBUTES) && (fileType & FILE_ATTRIBUTE_DIRECTORY);
90 return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
92 const auto widePath(convertMultiByteToWide(path));
93 if (!widePath.first) {
96 return CreateDirectoryW(widePath.first.get(),
nullptr) || GetLastError() == ERROR_ALREADY_EXISTS;
100 TestApplication *TestApplication::m_instance =
nullptr;
112 TestApplication::TestApplication(
int argc,
char **argv)
113 : m_helpArg(m_parser)
114 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files")
115 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested")
116 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files")
117 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units")
121 throw runtime_error(
"only one TestApplication instance allowed at a time");
127 m_fallbackTestFilesPath = readTestfilePathFromEnv();
129 bool fallbackIsSourceDir = m_fallbackTestFilesPath.empty();
130 if (fallbackIsSourceDir) {
131 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
137 for (
Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
138 arg->setRequiredValueCount(1);
139 arg->setValueNames({
"path" });
140 arg->setCombinable(
true);
145 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
150 }
catch (
const Failure &failure) {
163 cerr <<
"Directories used to search for testfiles:" << endl;
165 if (*m_testFilesPathArg.
values().front()) {
166 cerr << ((m_testFilesPath = m_testFilesPathArg.
values().front()) +=
'/') << endl;
168 cerr << (m_testFilesPath =
"./") << endl;
172 m_testFilesPath.swap(m_fallbackTestFilesPath);
173 cerr << m_testFilesPath << endl;
176 if (m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
177 m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
178 fallbackIsSourceDir =
true;
180 if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
181 cerr << m_fallbackTestFilesPath << endl;
183 cerr <<
"./testfiles/" << endl << endl;
184 cerr <<
"Directory used to store working copies:" << endl;
186 if (*m_workingDirArg.
values().front()) {
187 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
191 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
192 if (*workingDirEnv) {
197 m_workingDir = m_testFilesPath +
"workingdir/";
198 }
else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
199 m_workingDir = m_fallbackTestFilesPath +
"workingdir/";
201 m_workingDir =
"./testfiles/workingdir/";
204 cerr << m_workingDir << endl << endl;
207 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
208 ofstream(profrawListFile, ios_base::trunc);
212 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
220 m_instance =
nullptr;
241 if (!m_testFilesPath.empty()) {
242 if (
fileExists(path = m_testFilesPath + relativeTestFilePath)) {
248 if (!m_fallbackTestFilesPath.empty()) {
249 if (
fileExists(path = m_fallbackTestFilesPath + relativeTestFilePath)) {
255 if (!
fileExists(path =
"./testfiles/" + relativeTestFilePath)) {
256 cerr << Phrases::Warning <<
"The testfile \"" << relativeTestFilePath <<
"\" can not be located." << Phrases::EndFlush;
297 const std::string &relativeTestFilePath,
const std::string &relativeWorkingCopyPath,
WorkingCopyMode mode)
const 301 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": can't create working directory \"" 302 << m_workingDir <<
"\"." << Phrases::EndFlush;
307 const auto parts = splitString<vector<string>>(relativeWorkingCopyPath,
"/", EmptyPartsTreat::Omit);
308 if (!parts.empty()) {
311 currentLevel.reserve(m_workingDir.size() + relativeWorkingCopyPath.size() + 1);
312 currentLevel.assign(m_workingDir);
313 for (
auto i = parts.cbegin(), end = parts.end() - 1;
i != end; ++
i) {
314 if (currentLevel.back() !=
'/') {
324 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeWorkingCopyPath <<
"\": can't create directory \"" 325 << currentLevel <<
"\" (inside working directory)." << Phrases::EndFlush;
332 return m_workingDir + relativeWorkingCopyPath;
336 const auto origFilePath(
testFilePath(relativeTestFilePath));
338 size_t workingCopyPathAttempt = 0;
340 origFile.open(origFilePath, ios_base::in | ios_base::binary);
341 if (origFile.fail()) {
342 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath
343 <<
"\": an IO error occurred when opening original file \"" << origFilePath <<
"\"." << Phrases::EndFlush;
344 cerr <<
"error: " << strerror(errno) << endl;
347 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
352 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
354 if (workingCopy.fail()) {
355 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath
356 <<
"\": an IO error occurred when opening target file \"" <<
workingCopyPath <<
"\"." << Phrases::EndFlush;
357 cerr <<
"error: " << strerror(errno) << endl;
360 workingCopy << origFile.rdbuf();
361 if (!origFile.fail() && !workingCopy.fail()) {
365 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": ";
366 if (origFile.fail()) {
367 cerr <<
"an IO error occurred when reading original file \"" << origFilePath <<
"\"";
370 if (workingCopy.fail()) {
371 if (origFile.fail()) {
374 cerr <<
" an IO error occurred when writing to target file \"" <<
workingCopyPath <<
"\".";
376 cerr <<
"error: " << strerror(errno) << endl;
385 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
386 const std::string &newProfilingPath)
389 if (!suppressLogging) {
391 cout <<
'-' <<
' ' << appPath;
393 for (
const char *
const *
i = args + 1; *
i; ++
i) {
401 int coutPipes[2], cerrPipes[2];
404 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
405 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
408 if (
const auto child = fork()) {
410 close(writeCoutPipe);
411 close(writeCerrPipe);
415 throw runtime_error(
"Unable to create fork");
419 struct pollfd fileDescriptorSet[2];
420 fileDescriptorSet[0].fd = readCoutPipe;
421 fileDescriptorSet[1].fd = readCerrPipe;
422 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
431 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
433 throw runtime_error(
"Poll time-out");
436 throw runtime_error(
"Poll failed");
438 if (fileDescriptorSet[0].revents & POLLIN) {
439 const auto count = read(readCoutPipe, buffer,
sizeof(buffer));
441 output.append(buffer, static_cast<size_t>(count));
443 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
445 fileDescriptorSet[0].fd = -1;
447 if (fileDescriptorSet[1].revents & POLLIN) {
448 const auto count = read(readCerrPipe, buffer,
sizeof(buffer));
450 errors.append(buffer, static_cast<size_t>(count));
452 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
454 fileDescriptorSet[1].fd = -1;
456 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
466 waitpid(child, &childReturnCode, 0);
467 return childReturnCode;
471 dup2(writeCoutPipe, STDOUT_FILENO);
472 dup2(writeCerrPipe, STDERR_FILENO);
474 close(writeCoutPipe);
476 close(writeCerrPipe);
479 if (!newProfilingPath.empty()) {
480 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
484 execv(appPath, const_cast<char *const *>(args));
485 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
499 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 502 static unsigned int invocationCount = 0;
507 string fallbackAppPath;
512 const char *
const testAppPath = m_parser.
executable();
513 const size_t testAppPathLength = strlen(testAppPath);
514 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
515 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
516 appPath = fallbackAppPath.data();
519 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
524 string newProfilingPath;
525 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
527 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
528 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
530 const char *appName = strrchr(
appPath,
'/');
531 appName = appName ? appName + 1 :
appPath;
533 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
535 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
536 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
541 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
551 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
553 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
555 #endif // PLATFORM_UNIX 557 string TestApplication::readTestfilePathFromEnv()
559 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
560 if (!testFilesPathEnv || !*testFilesPathEnv) {
566 string TestApplication::readTestfilePathFromSrcRef()
571 auto srcDirContent(
readFile(
"srcdirref", 2 * 1024));
572 if (srcDirContent.empty()) {
573 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
578 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 579 bool hasTestfilesDir =
false;
580 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
581 if (dir ==
"testfiles") {
582 hasTestfilesDir =
true;
586 if (!hasTestfilesDir) {
587 cerr << Phrases::Warning
588 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 589 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
592 #endif // PLATFORM_UNIX 594 return srcDirContent +=
"/testfiles/";
596 cerr << Phrases::Warning <<
"The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory." 597 << 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.
bool fileSystemItemExists(const string &path)
std::string workingCopyPath(const std::string &relativeTestFilePath) const
Returns the full path to a working copy of the test file with the specified relativeTestFilePath.
bool makeDir(const string &path)
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.
std::fstream NativeFileStream
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.
bool fileExists(const string &path)
static const char * appPath()
Returns the application path or an empty string if no application path has been set.
std::string testFilePath(const std::string &relativeTestFilePath) const
Returns the full path of the test file with the specified relativeTestFilePath.
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.
std::string workingCopyPathMode(const std::string &relativeTestFilePath, WorkingCopyMode mode) const
Returns the full path to a working copy of the test file with the specified relativeTestFilePath.
const char * executable() const
Returns the name of the current executable.
The Argument class is a wrapper for command line argument information.
void setValueNames(std::initializer_list< const char * > valueNames)
Sets the names of the requried values.
bool dirExists(const string &path)
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.
std::string workingCopyPathAs(const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath, WorkingCopyMode mode=WorkingCopyMode::CreateCopy) const
Returns the full path to a working copy of the test file with the specified relativeTestFilePath.