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 + name)) {
248 if (!m_fallbackTestFilesPath.empty()) {
249 if (
fileExists(path = m_fallbackTestFilesPath + name)) {
255 if (!
fileExists(path =
"./testfiles/" + name)) {
256 cerr << Phrases::Warning <<
"The testfile \"" << name <<
"\" can not be located." << Phrases::EndFlush;
273 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory \"" << m_workingDir <<
"\"." 274 << Phrases::EndFlush;
279 const auto parts = splitString<vector<string>>(name,
"/", EmptyPartsTreat::Omit);
280 if (!parts.empty()) {
283 currentLevel.reserve(m_workingDir.size() + name.size() + 1);
284 currentLevel.assign(m_workingDir);
285 for (
auto i = parts.cbegin(), end = parts.end() - 1;
i != end; ++
i) {
286 if (currentLevel.back() !=
'/') {
296 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create directory \"" << currentLevel
297 <<
"\" (inside working directory)." << Phrases::EndFlush;
304 return m_workingDir + name;
310 size_t workingCopyPathAttempt = 0;
312 origFile.open(origFilePath, ios_base::in | ios_base::binary);
313 if (origFile.fail()) {
314 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occurred when opening original file \"" 315 << origFilePath <<
"\"." << Phrases::EndFlush;
316 cerr <<
"error: " << strerror(errno) << endl;
319 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
324 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
326 if (workingCopy.fail()) {
327 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occurred when opening target file \"" 329 cerr <<
"error: " << strerror(errno) << endl;
332 workingCopy << origFile.rdbuf();
333 if (!origFile.fail() && !workingCopy.fail()) {
337 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": ";
338 if (origFile.fail()) {
339 cerr <<
"an IO error occurred when reading original file \"" << origFilePath <<
"\"";
342 if (workingCopy.fail()) {
343 if (origFile.fail()) {
346 cerr <<
" an IO error occurred when writing to target file \"" <<
workingCopyPath <<
"\".";
348 cerr <<
"error: " << strerror(errno) << endl;
369 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
370 const std::string &newProfilingPath)
373 if (!suppressLogging) {
375 cout <<
'-' <<
' ' << appPath;
377 for (
const char *
const *
i = args + 1; *
i; ++
i) {
385 int coutPipes[2], cerrPipes[2];
388 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
389 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
392 if (
const auto child = fork()) {
394 close(writeCoutPipe);
395 close(writeCerrPipe);
399 throw runtime_error(
"Unable to create fork");
403 struct pollfd fileDescriptorSet[2];
404 fileDescriptorSet[0].fd = readCoutPipe;
405 fileDescriptorSet[1].fd = readCerrPipe;
406 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
415 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
417 throw runtime_error(
"Poll time-out");
420 throw runtime_error(
"Poll failed");
422 if (fileDescriptorSet[0].revents & POLLIN) {
423 const auto count = read(readCoutPipe, buffer,
sizeof(buffer));
425 output.append(buffer, static_cast<size_t>(count));
427 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
429 fileDescriptorSet[0].fd = -1;
431 if (fileDescriptorSet[1].revents & POLLIN) {
432 const auto count = read(readCerrPipe, buffer,
sizeof(buffer));
434 errors.append(buffer, static_cast<size_t>(count));
436 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
438 fileDescriptorSet[1].fd = -1;
440 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
450 waitpid(child, &childReturnCode, 0);
451 return childReturnCode;
455 dup2(writeCoutPipe, STDOUT_FILENO);
456 dup2(writeCerrPipe, STDERR_FILENO);
458 close(writeCoutPipe);
460 close(writeCerrPipe);
463 if (!newProfilingPath.empty()) {
464 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
468 execv(appPath, const_cast<char *const *>(args));
469 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
483 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 486 static unsigned int invocationCount = 0;
491 string fallbackAppPath;
496 const char *
const testAppPath = m_parser.
executable();
497 const size_t testAppPathLength = strlen(testAppPath);
498 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
499 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
500 appPath = fallbackAppPath.data();
503 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
508 string newProfilingPath;
509 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
511 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
512 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
514 const char *appName = strrchr(
appPath,
'/');
515 appName = appName ? appName + 1 :
appPath;
517 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
519 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
520 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
525 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
535 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
537 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
539 #endif // PLATFORM_UNIX 541 string TestApplication::readTestfilePathFromEnv()
543 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
544 if (!testFilesPathEnv || !*testFilesPathEnv) {
550 string TestApplication::readTestfilePathFromSrcRef()
555 auto srcDirContent(
readFile(
"srcdirref", 2 * 1024));
556 if (srcDirContent.empty()) {
557 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
562 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 563 bool hasTestfilesDir =
false;
564 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
565 if (dir ==
"testfiles") {
566 hasTestfilesDir =
true;
570 if (!hasTestfilesDir) {
571 cerr << Phrases::Warning
572 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 573 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
576 #endif // PLATFORM_UNIX 578 return srcDirContent +=
"/testfiles/";
580 cerr << Phrases::Warning <<
"The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory." 581 << Phrases::EndFlush;
Encapsulates functions for formatted terminal output using ANSI escape codes.
std::string workingCopyPathMode(const std::string &name, WorkingCopyMode mode) const
Returns the full path to a working copy of the test file with the specified name. ...
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)
bool makeDir(const string &path)
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.
std::fstream NativeFileStream
std::string workingCopyPath(const std::string &name) const
Creates a working copy of the test file with the specified name and returns the full path of the crea...
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.
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.
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.
void setValueNames(std::initializer_list< const char *> valueNames)
Sets the names of the requried values.