3 #include "../conversion/stringbuilder.h"
4 #include "../conversion/stringconversion.h"
5 #include "../io/ansiescapecodes.h"
6 #include "../io/misc.h"
7 #include "../io/nativefilestream.h"
8 #include "../io/path.h"
9 #include "../misc/parseerror.h"
15 #include <initializer_list>
20 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
29 #ifdef PLATFORM_WINDOWS
42 bool fileSystemItemExists(
const string &path)
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;
57 bool fileExists(
const string &path)
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);
72 bool dirExists(
const string &path)
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);
87 bool makeDir(
const string &path)
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;
101 TestApplication *TestApplication::s_instance =
nullptr;
115 TestApplication::TestApplication()
125 : m_listArg(
"list",
'l',
"lists available test units")
126 , m_runArg(
"run",
'r',
"runs the tests")
127 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files", {
"path" })
128 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested", {
"path" })
129 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files", {
"path" })
130 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units", {
"unit1",
"unit2",
"unit3" })
134 throw runtime_error(
"only one TestApplication instance allowed at a time");
143 m_runArg.setImplicit(
true);
144 m_runArg.setSubArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg });
145 m_parser.setMainArguments({ &m_runArg, &m_listArg, &m_parser.noColorArg(), &m_parser.helpArg() });
150 }
catch (
const ParseError &failure) {
157 if (m_parser.helpArg().isPresent()) {
164 if (m_testFilesPathArg.isPresent()) {
165 for (
const char *
const testFilesPath : m_testFilesPathArg.values()) {
166 if (*testFilesPath) {
167 m_testFilesPaths.emplace_back(
argsToString(testFilesPath,
'/'));
169 m_testFilesPaths.emplace_back(
"./");
174 bool hasTestFilePathFromEnv;
175 if (
auto testFilePathFromEnv = readTestfilePathFromEnv(); (hasTestFilePathFromEnv = !testFilePathFromEnv.empty())) {
176 m_testFilesPaths.emplace_back(move(testFilePathFromEnv));
179 if (
auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
180 m_testFilesPaths.emplace_back(move(testFilePathFromSrcDirRef));
183 m_testFilesPaths.emplace_back(
"./testfiles/");
184 for (
const auto &testFilesPath : m_testFilesPaths) {
185 cerr << testFilesPath <<
'\n';
189 if (m_workingDirArg.isPresent()) {
190 if (*m_workingDirArg.values().front()) {
191 (m_workingDir = m_workingDirArg.values().front()) +=
'/';
195 }
else if (
const char *
const workingDirEnv = getenv(
"WORKING_DIR")) {
196 if (*workingDirEnv) {
200 if ((m_testFilesPathArg.isPresent() && !m_testFilesPathArg.values().empty()) || hasTestFilePathFromEnv) {
201 m_workingDir = m_testFilesPaths.front() +
"workingdir/";
203 m_workingDir =
"./testfiles/workingdir/";
206 cerr <<
"Directory used to store working copies:\n" << m_workingDir <<
'\n';
209 if (
const char *
const profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
210 ofstream(profrawListFile, ios_base::trunc);
221 s_instance =
nullptr;
239 for (
const auto &testFilesPath : m_testFilesPaths) {
240 if (fileExists(path = testFilesPath + relativeTestFilePath)) {
244 throw std::runtime_error(
"The test file \"" % relativeTestFilePath %
"\" can not be located. Was looking under:\n"
245 +
joinStrings(m_testFilesPaths,
"\n",
false,
" - ", relativeTestFilePath));
257 for (
const auto &testFilesPath : m_testFilesPaths) {
258 if (dirExists(path = testFilesPath + relativeTestDirPath)) {
262 throw std::runtime_error(
"The test directory \"" % relativeTestDirPath %
"\" can not be located. Was looking under:\n"
263 +
joinStrings(m_testFilesPaths,
"\n",
false,
" - ", relativeTestDirPath));
293 const std::string &relativeTestFilePath,
const std::string &relativeWorkingCopyPath,
WorkingCopyMode mode)
const
296 if (!dirExists(m_workingDir) && !makeDir(m_workingDir)) {
297 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": can't create working directory \""
298 << m_workingDir <<
"\"." << Phrases::EndFlush;
304 if (!parts.empty()) {
307 currentLevel.reserve(m_workingDir.size() + relativeWorkingCopyPath.size() + 1);
308 currentLevel.assign(m_workingDir);
309 for (
auto i = parts.cbegin(), end = parts.end() - 1;
i != end; ++
i) {
310 if (currentLevel.back() !=
'/') {
316 if (dirExists(currentLevel) || makeDir(currentLevel)) {
320 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeWorkingCopyPath <<
"\": can't create directory \""
321 << currentLevel <<
"\" (inside working directory)." << Phrases::EndFlush;
328 return m_workingDir + relativeWorkingCopyPath;
332 const auto origFilePath(
testFilePath(relativeTestFilePath));
334 size_t workingCopyPathAttempt = 0;
336 origFile.open(origFilePath, ios_base::in | ios_base::binary);
337 if (origFile.fail()) {
338 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath
339 <<
"\": an IO error occurred when opening original file \"" << origFilePath <<
"\"." << Phrases::EndFlush;
340 cerr <<
"error: " << strerror(errno) << endl;
343 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
348 workingCopy.open(
workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
350 if (workingCopy.fail()) {
351 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath
352 <<
"\": an IO error occurred when opening target file \"" <<
workingCopyPath <<
"\"." << Phrases::EndFlush;
353 cerr <<
"error: " << strerror(errno) << endl;
356 workingCopy << origFile.rdbuf();
358 if (!origFile.fail() && !workingCopy.fail()) {
362 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": ";
363 if (origFile.fail()) {
364 cerr <<
"an IO error occurred when reading original file \"" << origFilePath <<
"\"";
367 if (workingCopy.fail()) {
368 if (origFile.fail()) {
371 cerr <<
" an IO error occurred when writing to target file \"" <<
workingCopyPath <<
"\".";
373 cerr <<
"error: " << strerror(errno) << endl;
382 static int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
383 const std::string &newProfilingPath,
bool enableSearchPath =
false)
386 if (!suppressLogging) {
388 cout <<
'-' <<
' ' << appPath;
390 for (
const char *
const *
i = args + 1; *
i; ++
i) {
398 int coutPipes[2], cerrPipes[2];
401 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
402 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
405 if (
const auto child = fork()) {
407 close(writeCoutPipe);
408 close(writeCerrPipe);
412 throw runtime_error(
"Unable to create fork");
416 struct pollfd fileDescriptorSet[2];
417 fileDescriptorSet[0].fd = readCoutPipe;
418 fileDescriptorSet[1].fd = readCerrPipe;
419 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
428 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
430 throw runtime_error(
"Poll time-out");
433 throw runtime_error(
"Poll failed");
435 if (fileDescriptorSet[0].revents & POLLIN) {
436 const auto count = read(readCoutPipe, buffer,
sizeof(buffer));
438 output.append(buffer,
static_cast<size_t>(count));
440 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
442 fileDescriptorSet[0].fd = -1;
444 if (fileDescriptorSet[1].revents & POLLIN) {
445 const auto count = read(readCerrPipe, buffer,
sizeof(buffer));
447 errors.append(buffer,
static_cast<size_t>(count));
449 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
451 fileDescriptorSet[1].fd = -1;
453 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
463 waitpid(child, &childReturnCode, 0);
464 return childReturnCode;
468 dup2(writeCoutPipe, STDOUT_FILENO);
469 dup2(writeCerrPipe, STDERR_FILENO);
471 close(writeCoutPipe);
473 close(writeCerrPipe);
476 if (!newProfilingPath.empty()) {
477 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
481 if (enableSearchPath) {
482 execvp(appPath,
const_cast<char *
const *
>(args));
485 execv(appPath,
const_cast<char *
const *
>(args));
487 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
501 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const
504 static unsigned int invocationCount = 0;
509 auto fallbackAppPath = string();
514 const char *
const testAppPath = m_parser.
executable();
515 const auto testAppPathLength = strlen(testAppPath);
516 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
517 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
518 appPath = fallbackAppPath.data();
521 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
526 const auto newProfilingPath = [
appPath] {
527 auto path = string();
528 const char *
const llvmProfileFile = getenv(
"LLVM_PROFILE_FILE");
529 if (!llvmProfileFile) {
533 const char *
const llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw");
534 if (!llvmProfileFileEnd) {
537 const auto llvmProfileFileWithoutExtension = string(llvmProfileFile, llvmProfileFileEnd);
539 const char *appName = strrchr(
appPath,
'/');
540 appName = appName ? appName + 1 :
appPath;
542 path =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
544 if (
const char *
const profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
545 ofstream(profrawListFile, ios_base::app) << path << endl;
550 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
560 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
562 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
575 int execHelperAppInSearchPath(
576 const char *appName,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
578 return execAppInternal(appName, args, output, errors, suppressLogging, timeout,
string(),
true);
585 string TestApplication::readTestfilePathFromEnv()
587 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
588 if (!testFilesPathEnv || !*testFilesPathEnv) {
599 string TestApplication::readTestfilePathFromSrcRef()
603 std::string binaryPath;
604 #if defined(CPP_UTILITIES_USE_STANDARD_FILESYSTEM) && defined(PLATFORM_UNIX)
606 binaryPath = std::filesystem::read_symlink(
"/proc/self/exe").parent_path();
608 }
catch (
const std::filesystem::filesystem_error &e) {
609 cerr << Phrases::Warning <<
"Unable to detect binary path for finding \"srcdirref\": " << e.what() << Phrases::EndFlush;
614 auto srcDirContent(
readFile(binaryPath +
"srcdirref", 2 * 1024));
615 if (srcDirContent.empty()) {
616 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
619 srcDirContent +=
"/testfiles/";
622 if (!dirExists(srcDirContent)) {
623 cerr << Phrases::Warning
624 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
625 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
628 return srcDirContent;
630 }
catch (
const std::ios_base::failure &) {
631 cerr << Phrases::Warning <<
"The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory."
632 << Phrases::EndFlush;
const char * executable() const
Returns the name of the current executable.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
The TestApplication class simplifies writing test applications that require opening test files.
std::string workingCopyPath(const std::string &relativeTestFilePath, WorkingCopyMode mode=WorkingCopyMode::CreateCopy) const
Returns the full path to a working copy of the test file with the specified relativeTestFilePath.
std::string testFilePath(const std::string &relativeTestFilePath) const
Returns the full path of the test file with the specified relativeTestFilePath.
static const char * appPath()
Returns the application path or an empty string if no application path has been set.
TestApplication()
Constructs a TestApplication instance without further arguments.
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.
std::string testDirPath(const std::string &relativeTestDirPath) const
Returns the full path of the test directory with the specified relativeTestDirPath.
~TestApplication()
Destroys the TestApplication.
Encapsulates functions for formatted terminal output using ANSI escape codes.
Contains all utilities provides by the c++utilities library.
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.
WorkingCopyMode
The WorkingCopyMode enum specifies additional options to influence behavior of TestApplication::worki...
ReturnType joinStrings(const Container &strings, Detail::StringParamForContainer< Container > delimiter=Detail::StringParamForContainer< Container >(), bool omitEmpty=false, Detail::StringParamForContainer< Container > leftClosure=Detail::StringParamForContainer< Container >(), Detail::StringParamForContainer< Container > rightClosure=Detail::StringParamForContainer< Container >())
Joins the given strings using the specified delimiter.
StringType argsToString(Args &&...args)
std::fstream NativeFileStream