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>
26 #ifdef PLATFORM_WINDOWS
42 return stat(path.data(), &res) == 0;
44 const auto widePath(convertMultiByteToWide(path));
45 if (!widePath.first) {
48 const auto fileType(GetFileAttributesW(widePath.first.get()));
49 return fileType != INVALID_FILE_ATTRIBUTES;
57 return stat(path.data(), &res) == 0 && !S_ISDIR(res.st_mode);
59 const auto widePath(convertMultiByteToWide(path));
60 if (!widePath.first) {
63 const auto fileType(GetFileAttributesW(widePath.first.get()));
64 return (fileType != INVALID_FILE_ATTRIBUTES) && !(fileType & FILE_ATTRIBUTE_DIRECTORY) && !(fileType & FILE_ATTRIBUTE_DEVICE);
72 return stat(path.data(), &res) == 0 && S_ISDIR(res.st_mode);
74 const auto widePath(convertMultiByteToWide(path));
75 if (!widePath.first) {
78 const auto fileType(GetFileAttributesW(widePath.first.get()));
79 return (fileType != INVALID_FILE_ATTRIBUTES) && (fileType & FILE_ATTRIBUTE_DIRECTORY);
86 return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
88 const auto widePath(convertMultiByteToWide(path));
89 if (!widePath.first) {
92 return CreateDirectoryW(widePath.first.get(),
nullptr) || GetLastError() == ERROR_ALREADY_EXISTS;
96 TestApplication *TestApplication::s_instance =
nullptr;
108 TestApplication::TestApplication()
118 : m_listArg(
"list",
'l',
"lists available test units")
119 , m_runArg(
"run",
'r',
"runs the tests")
120 , m_testFilesPathArg(
"test-files-path",
'p',
"specifies the path of the directory with test files", {
"path" })
121 , m_applicationPathArg(
"app-path",
'a',
"specifies the path of the application to be tested", {
"path" })
122 , m_workingDirArg(
"working-dir",
'w',
"specifies the directory to store working copies of test files", {
"path" })
123 , m_unitsArg(
"units",
'u',
"specifies the units to test; omit to test all units", {
"unit1",
"unit2",
"unit3" })
127 throw runtime_error(
"only one TestApplication instance allowed at a time");
136 m_runArg.setImplicit(
true);
137 m_runArg.setSubArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg });
138 m_parser.setMainArguments({ &m_runArg, &m_listArg, &m_parser.noColorArg(), &m_parser.helpArg() });
143 }
catch (
const ParseError &failure) {
150 if (m_parser.helpArg().isPresent()) {
157 if (m_testFilesPathArg.isPresent()) {
158 for (
const char *
const testFilesPath : m_testFilesPathArg.values()) {
159 if (*testFilesPath) {
160 m_testFilesPaths.emplace_back(
argsToString(testFilesPath,
'/'));
162 m_testFilesPaths.emplace_back(
"./");
167 bool hasTestFilePathFromEnv;
168 if (
auto testFilePathFromEnv = readTestfilePathFromEnv(); (hasTestFilePathFromEnv = !testFilePathFromEnv.empty())) {
169 m_testFilesPaths.emplace_back(move(testFilePathFromEnv));
172 if (
auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
173 m_testFilesPaths.emplace_back(move(testFilePathFromSrcDirRef));
176 m_testFilesPaths.emplace_back(
"./testfiles/");
177 for (
const auto &testFilesPath : m_testFilesPaths) {
178 cerr << testFilesPath <<
'\n';
182 if (m_workingDirArg.isPresent()) {
183 if (*m_workingDirArg.values().front()) {
184 (m_workingDir = m_workingDirArg.values().front()) +=
'/';
188 }
else if (
const char *
const workingDirEnv = getenv(
"WORKING_DIR")) {
189 if (*workingDirEnv) {
193 if ((m_testFilesPathArg.isPresent() && !m_testFilesPathArg.values().empty()) || hasTestFilePathFromEnv) {
194 m_workingDir = m_testFilesPaths.front() +
"workingdir/";
196 m_workingDir =
"./testfiles/workingdir/";
199 cerr <<
"Directory used to store working copies:\n" << m_workingDir <<
'\n';
202 if (
const char *
const profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
203 ofstream(profrawListFile, ios_base::trunc);
207 cerr << TextAttribute::Bold <<
"Executing test cases ..." << Phrases::EndFlush;
215 s_instance =
nullptr;
233 for (
const auto &testFilesPath : m_testFilesPaths) {
234 if (
fileExists(path = testFilesPath + relativeTestFilePath)) {
238 throw runtime_error(
"The testfile \"" % relativeTestFilePath %
"\" can not be located. Was looking under:"
239 +
joinStrings(m_testFilesPaths,
"\n",
false,
string(), relativeTestFilePath));
269 const std::string &relativeTestFilePath,
const std::string &relativeWorkingCopyPath,
WorkingCopyMode mode)
const
273 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": can't create working directory \""
274 << m_workingDir <<
"\"." << Phrases::EndFlush;
280 if (!parts.empty()) {
283 currentLevel.reserve(m_workingDir.size() + relativeWorkingCopyPath.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 \"" << relativeWorkingCopyPath <<
"\": can't create directory \""
297 << currentLevel <<
"\" (inside working directory)." << Phrases::EndFlush;
304 return m_workingDir + relativeWorkingCopyPath;
308 const auto origFilePath(
testFilePath(relativeTestFilePath));
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 \"" << relativeTestFilePath
315 <<
"\": an IO error occurred when opening original file \"" << 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 \"" << relativeTestFilePath
328 <<
"\": an IO error occurred when opening target file \"" <<
workingCopyPath <<
"\"." << Phrases::EndFlush;
329 cerr <<
"error: " << strerror(errno) << endl;
332 workingCopy << origFile.rdbuf();
334 if (!origFile.fail() && !workingCopy.fail()) {
338 cerr << Phrases::Error <<
"Unable to create working copy for \"" << relativeTestFilePath <<
"\": ";
339 if (origFile.fail()) {
340 cerr <<
"an IO error occurred when reading original file \"" << origFilePath <<
"\"";
343 if (workingCopy.fail()) {
344 if (origFile.fail()) {
347 cerr <<
" an IO error occurred when writing to target file \"" <<
workingCopyPath <<
"\".";
349 cerr <<
"error: " << strerror(errno) << endl;
358 int execAppInternal(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout,
359 const std::string &newProfilingPath)
362 if (!suppressLogging) {
364 cout <<
'-' <<
' ' << appPath;
366 for (
const char *
const *
i = args + 1; *
i; ++
i) {
374 int coutPipes[2], cerrPipes[2];
377 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
378 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
381 if (
const auto child = fork()) {
383 close(writeCoutPipe);
384 close(writeCerrPipe);
388 throw runtime_error(
"Unable to create fork");
392 struct pollfd fileDescriptorSet[2];
393 fileDescriptorSet[0].fd = readCoutPipe;
394 fileDescriptorSet[1].fd = readCerrPipe;
395 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
404 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
406 throw runtime_error(
"Poll time-out");
409 throw runtime_error(
"Poll failed");
411 if (fileDescriptorSet[0].revents & POLLIN) {
412 const auto count = read(readCoutPipe, buffer,
sizeof(buffer));
414 output.append(buffer,
static_cast<size_t>(count));
416 }
else if (fileDescriptorSet[0].revents & POLLHUP) {
418 fileDescriptorSet[0].fd = -1;
420 if (fileDescriptorSet[1].revents & POLLIN) {
421 const auto count = read(readCerrPipe, buffer,
sizeof(buffer));
423 errors.append(buffer,
static_cast<size_t>(count));
425 }
else if (fileDescriptorSet[1].revents & POLLHUP) {
427 fileDescriptorSet[1].fd = -1;
429 }
while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
439 waitpid(child, &childReturnCode, 0);
440 return childReturnCode;
444 dup2(writeCoutPipe, STDOUT_FILENO);
445 dup2(writeCerrPipe, STDERR_FILENO);
447 close(writeCoutPipe);
449 close(writeCerrPipe);
452 if (!newProfilingPath.empty()) {
453 setenv(
"LLVM_PROFILE_FILE", newProfilingPath.data(),
true);
457 execv(appPath,
const_cast<char *
const *
>(args));
458 cerr << Phrases::Error <<
"Unable to execute \"" << appPath <<
"\": execv() failed" << Phrases::EndFlush;
472 int TestApplication::execApp(
const char *
const *args,
string &output,
string &errors,
bool suppressLogging,
int timeout)
const
475 static unsigned int invocationCount = 0;
480 string fallbackAppPath;
485 const char *
const testAppPath = m_parser.
executable();
486 const size_t testAppPathLength = strlen(testAppPath);
487 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6,
"_tests")) {
488 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
489 appPath = fallbackAppPath.data();
492 throw runtime_error(
"Unable to execute application to be tested: no application path specified");
497 const auto newProfilingPath = [
appPath] {
498 string newProfilingPath;
499 const char *
const llvmProfileFile = getenv(
"LLVM_PROFILE_FILE");
500 if (!llvmProfileFile) {
501 return newProfilingPath;
504 const char *
const llvmProfileFileEnd = strstr(llvmProfileFile,
".profraw");
505 if (!llvmProfileFileEnd) {
506 return newProfilingPath;
508 const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
510 const char *appName = strrchr(
appPath,
'/');
511 appName = appName ? appName + 1 :
appPath;
513 newProfilingPath =
argsToString(llvmProfileFileWithoutExtension,
'_', appName, invocationCount,
".profraw");
515 if (
const char *
const profrawListFile = getenv(
"LLVM_PROFILE_LIST_FILE")) {
516 ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
518 return newProfilingPath;
521 return execAppInternal(
appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
531 int execHelperApp(
const char *appPath,
const char *
const *args, std::string &output, std::string &errors,
bool suppressLogging,
int timeout)
533 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout,
string());
535 #endif // PLATFORM_UNIX
537 string TestApplication::readTestfilePathFromEnv()
539 const char *
const testFilesPathEnv = getenv(
"TEST_FILE_PATH");
540 if (!testFilesPathEnv || !*testFilesPathEnv) {
546 string TestApplication::readTestfilePathFromSrcRef()
551 auto srcDirContent(
readFile(
"srcdirref", 2 * 1024));
552 if (srcDirContent.empty()) {
553 cerr << Phrases::Warning <<
"The file \"srcdirref\" is empty." << Phrases::EndFlush;
556 srcDirContent +=
"/testfiles/";
560 cerr << Phrases::Warning
561 <<
"The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
562 << Phrases::End <<
"Referenced source directory: " << srcDirContent << endl;
565 return srcDirContent;
567 }
catch (
const std::ios_base::failure &) {
568 cerr << Phrases::Warning <<
"The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory."
569 << Phrases::EndFlush;