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();
71 for (
Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
72 arg->setRequiredValueCount(1);
73 arg->setValueNames({
"path" });
74 arg->setCombinable(
true);
79 m_parser.
setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
84 }
catch (
const Failure &failure) {
97 cerr <<
"Directories used to search for testfiles:" << endl;
99 if (*m_testFilesPathArg.
values().front()) {
100 cerr << ((m_testFilesPath = m_testFilesPathArg.
values().front()) +=
'/') << endl;
102 cerr << (m_testFilesPath =
"./") << endl;
105 if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
106 cerr << m_fallbackTestFilesPath << endl;
108 cerr <<
"./testfiles/" << endl << endl;
109 cerr <<
"Directory used to store working copies:" << endl;
111 if (*m_workingDirArg.
values().front()) {
112 (m_workingDir = m_workingDirArg.
values().front()) +=
'/';
116 }
else if (
const char *workingDirEnv = getenv(
"WORKING_DIR")) {
117 if (
const auto len = strlen(workingDirEnv)) {
118 m_workingDir.reserve(len + 1);
119 m_workingDir += workingDirEnv;
124 m_workingDir = m_testFilesPath +
"workingdir/";
125 }
else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
126 m_workingDir = m_fallbackTestFilesPath +
"workingdir/";
128 m_workingDir =
"./testfiles/workingdir/";
131 cerr << m_workingDir << endl << endl;
134 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
135 ofstream(profrawListFile, ios_base::trunc);
139 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
147 m_instance =
nullptr;
160 file.open(path = m_testFilesPath + name, ios_base::in);
167 if (!m_fallbackTestFilesPath.empty()) {
169 file.open(path = m_fallbackTestFilesPath + name, ios_base::in);
177 file.open(path =
"./testfiles/" + name, ios_base::in);
179 cerr << Phrases::Warning <<
"The testfile \"" << path <<
"\" can not be located." << Phrases::EndFlush;
189 string TestApplication::workingCopyPathMode(
const string &name,
WorkingCopyMode mode)
const 192 fstream origFile, workingCopy;
193 origFile.exceptions(ios_base::badbit | ios_base::failbit);
194 workingCopy.exceptions(ios_base::badbit | ios_base::failbit);
197 struct stat currentStat;
198 if (stat(m_workingDir.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
199 if (mkdir(m_workingDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
200 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." << Phrases::EndFlush;
206 const auto parts = splitString<vector<string>>(name, string(
"/"), EmptyPartsTreat::Omit);
207 if (!parts.empty()) {
208 string currentLevel = m_workingDir;
209 for (
auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
210 if (currentLevel.back() !=
'/') {
214 if (stat(currentLevel.c_str(), ¤tStat) || !S_ISDIR(currentStat.st_mode)) {
215 if (mkdir(currentLevel.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
216 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": can't create working directory." 217 << Phrases::EndFlush;
227 origFile.open(
testFilePath(name), ios_base::in | ios_base::binary);
228 const string path = m_workingDir + name;
229 workingCopy.open(path, ios_base::out | ios_base::binary | ios_base::trunc);
230 workingCopy << origFile.rdbuf();
234 cerr << Phrases::Error <<
"Unable to create working copy for \"" << name <<
"\": an IO error occured." << Phrases::EndFlush;
237 return m_workingDir + name;
242 string TestApplication::workingCopyPath(
const string &name)
const 251 int execAppInternal(
const char *
appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
252 const std::string &newProfilingPath)
255 if (!suppressLogging) {
257 for (
const char *
const *i = args; *i; ++i) {
264 int coutPipes[2], cerrPipes[2];
265 pipe(coutPipes), pipe(cerrPipes);
266 int readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
267 int readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
270 if (
int child = fork()) {
272 close(writeCoutPipe), close(writeCerrPipe);
276 throw runtime_error(
"Unable to create fork");
280 struct pollfd fileDescriptorSet[2];
281 fileDescriptorSet[0].fd = readCoutPipe;
282 fileDescriptorSet[1].fd = readCerrPipe;
283 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
288 output.clear(), errors.clear();
292 int retpoll = poll(fileDescriptorSet, 2, timeout);
295 if (fileDescriptorSet[0].revents & POLLIN) {
296 if ((count = read(readCoutPipe, buffer,
sizeof(buffer))) > 0) {
297 output.append(buffer, static_cast<size_t>(count));
299 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
301 fileDescriptorSet[0].fd = -1;
303 if (fileDescriptorSet[1].revents & POLLIN) {
304 if ((count = read(readCerrPipe, buffer,
sizeof(buffer))) > 0) {
305 errors.append(buffer, static_cast<size_t>(count));
307 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
309 fileDescriptorSet[1].fd = -1;
311 }
else if (retpoll == 0) {
313 throw runtime_error(
"Poll time-out");
316 throw runtime_error(
"Poll failed");
318 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
321 close(readCoutPipe), close(readCerrPipe);
327 waitpid(child, &childReturnCode, 0);
328 return childReturnCode;
332 dup2(writeCoutPipe, STDOUT_FILENO), dup2(writeCerrPipe, STDERR_FILENO);
333 close(readCoutPipe), close(writeCoutPipe), close(readCerrPipe), close(writeCerrPipe);
336 if (!newProfilingPath.empty()) {
337 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
341 execv(appPath, const_cast<char *const *>(args));
342 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
356 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const 359 static unsigned int invocationCount = 0;
363 const char *appPath = m_applicationPathArg.
firstValue();
364 string fallbackAppPath;
365 if (!appPath || !*appPath) {
369 const char *
const testAppPath = m_parser.
executable();
370 const size_t testAppPathLength = strlen(testAppPath);
371 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
372 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
373 appPath = fallbackAppPath.data();
376 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
381 string newProfilingPath;
382 if (
const char *llvmProfileFile = getenv(
"LLVM_PROFILE_FILE")) {
384 if (
const char *llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw")) {
385 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
387 const char *appName = strrchr(appPath,
'/');
388 appName = appName ? appName + 1 :
appPath;
390 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
392 if (
const char *profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
393 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
398 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
408 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
410 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
412 #endif // PLATFORM_UNIX 414 void TestApplication::readFallbackTestfilePathFromEnv()
416 if (
const char *testFilesPathEnv = getenv(
"TEST_FILE_PATH")) {
417 if (
const auto len = strlen(testFilesPathEnv)) {
418 m_fallbackTestFilesPath.reserve(len + 1);
419 m_fallbackTestFilesPath += testFilesPathEnv;
420 m_fallbackTestFilesPath +=
'/';
425 void TestApplication::readFallbackTestfilePathFromSrcRef()
430 const string srcDirContent(
readFile(
"srcdirref", 2 * 1024));
431 if (srcDirContent.empty()) {
432 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
437 #ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX 438 bool hasTestfilesDir =
false;
439 for (
const string &dir :
directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
440 if (dir ==
"testfiles") {
441 hasTestfilesDir =
true;
445 if (!hasTestfilesDir) {
446 cerr << Phrases::Warning
447 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist." 448 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
451 #endif // PLATFORM_UNIX 453 m_fallbackTestFilesPath = move(srcDirContent);
454 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.