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 readFallbackTestfilePathFromEnv();
65 const bool fallbackIsSourceDir = m_fallbackTestFilesPath.empty();
66 if (fallbackIsSourceDir) {
67 readFallbackTestfilePathFromSrcRef();
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;
107 if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
108 cerr << m_fallbackTestFilesPath << endl;
110 cerr <<
"./testfiles/" << endl << endl;
111 cerr <<
"Directory used to store working copies:" << endl;
113 if (*m_workingDirArg.
values().front()) {
114 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
118 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
119 if (
const auto len = strlen(workingDirEnv)) {
120 m_workingDir.reserve(len + 1);
121 m_workingDir += workingDirEnv;
126 m_workingDir = m_testFilesPath +
"workingdir/";
127 }
else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
128 m_workingDir = m_fallbackTestFilesPath +
"workingdir/";
130 m_workingDir =
"./testfiles/workingdir/";
133 cerr << m_workingDir << endl << endl;
136 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
137 ofstream(profrawListFile, ios_base::trunc);
141 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
149 m_instance =
nullptr;
169 file.open(path = m_testFilesPath + name, ios_base::in);
176 if (!m_fallbackTestFilesPath.empty()) {
178 file.open(path = m_fallbackTestFilesPath + name, ios_base::in);
186 file.open(path =
"./testfiles/" + name, ios_base::in);
188 cerr << Phrases::Warning <<
"The testfile \"" << path <<
"\" can not be located." << Phrases::EndFlush;
202 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 205 fstream origFile, workingCopy;
206 origFile.exceptions(ios_base::badbit | ios_base::failbit);
207 workingCopy.exceptions(ios_base::badbit | ios_base::failbit);
210 struct stat currentStat;
211 if (stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
212 if (mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
213 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << Phrases::EndFlush;
219 const auto parts = splitString<vector<string>>(name, string(
"/"), EmptyPartsTreat::Omit);
220 if (!parts.empty()) {
221 string currentLevel = m_workingDir;
222 for (
auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
223 if (currentLevel.back() !=
'/') {
227 if (stat(currentLevel.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
228 if (mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
229 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." 230 << Phrases::EndFlush;
240 origFile.open(
testFilePath(name), ios_base::in | ios_base::binary);
241 const string path = m_workingDir + name;
242 workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc);
243 workingCopy << origFile.rdbuf();
247 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occured." << Phrases::EndFlush;
250 return m_workingDir + name;
262 string TestApplication::workingCopyPath(
const string &name)
const 271 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
272 const std::string &newProfilingPath)
275 if (!suppressLogging) {
277 for (
const char *
const *i = args; *i; ++i) {
284 int coutPipes[2], cerrPipes[2];
285 pipe(coutPipes), pipe(cerrPipes);
286 int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
287 int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
290 if (
int child = fork()) {
292 close(writeCoutPipe), close(writeCerrPipe);
296 throw runtime_error(
"Unable to create fork");
300 struct pollfd fileDescriptorSet[2];
301 fileDescriptorSet[0].fd = readCoutPipe;
302 fileDescriptorSet[1].fd = readCerrPipe;
303 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
308 output.clear(), errors.clear();
312 int retpoll = poll(fileDescriptorSet, 2, timeout);
315 if (fileDescriptorSet[0].revents & POLLIN) {
316 if ((count = read(readCoutPipe, buffer,
sizeof(buffer))) > 0) {
317 output.append(buffer, static_cast<size_t>(count));
319 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
321 fileDescriptorSet[0].fd = -1;
323 if (fileDescriptorSet[1].revents & POLLIN) {
324 if ((count = read(readCerrPipe, buffer,
sizeof(buffer))) > 0) {
325 errors.append(buffer, static_cast<size_t>(count));
327 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
329 fileDescriptorSet[1].fd = -1;
331 }
else if (retpoll == 0) {
333 throw runtime_error(
"Poll time-out");
336 throw runtime_error(
"Poll failed");
338 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
341 close(readCoutPipe), close(readCerrPipe);
347 waitpid(child, &childReturnCode, 0);
348 return childReturnCode;
352 dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
353 close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
356 if (!newProfilingPath.empty()) {
357 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
361 execv(appPath, const_cast<char *const *>(args));
362 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
376 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 379 static unsigned int invocationCount = 0;
384 string fallbackAppPath;
389 const char *
const testAppPath = m_parser.
executable();
390 const size_t testAppPathLength = strlen(testAppPath);
391 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
392 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
393 appPath = fallbackAppPath.data();
396 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
401 string newProfilingPath;
402 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
404 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
405 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
407 const char *appName = strrchr(
appPath,
'/');
408 appName = appName ? appName + 1 :
appPath;
410 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
412 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
413 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
418 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
428 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
430 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
432 #endif // PLATFORM_UNIX 434 void TestApplication::readFallbackTestfilePathFromEnv()
436 if (
const char *testFilesPathEnv = getenv(
"TEST_FILE_PATH")) {
437 if (
const auto len = strlen(testFilesPathEnv)) {
438 m_fallbackTestFilesPath.reserve(len + 1);
439 m_fallbackTestFilesPath += testFilesPathEnv;
440 m_fallbackTestFilesPath +=
'/';
445 void TestApplication::readFallbackTestfilePathFromSrcRef()
450 const string srcDirContent(
readFile(
"srcdirref", 2 * 1024));
451 if (srcDirContent.empty()) {
452 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
457 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 458 bool hasTestfilesDir =
false;
459 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
460 if (dir ==
"testfiles") {
461 hasTestfilesDir =
true;
465 if (!hasTestfilesDir) {
466 cerr << Phrases::Warning
467 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 468 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
471 #endif // PLATFORM_UNIX 473 m_fallbackTestFilesPath = move(srcDirContent);
474 m_fallbackTestFilesPath +=
"/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.