2016-02-06 02:52:06 +01:00
|
|
|
#include "./testutils.h"
|
|
|
|
|
|
|
|
#include "../application/failure.h"
|
2017-06-20 23:19:49 +02:00
|
|
|
#include "../conversion/stringbuilder.h"
|
2016-02-17 20:21:11 +01:00
|
|
|
#include "../conversion/stringconversion.h"
|
2017-10-24 01:02:07 +02:00
|
|
|
#include "../io/ansiescapecodes.h"
|
2016-06-14 22:53:19 +02:00
|
|
|
#include "../io/catchiofailure.h"
|
2017-10-30 23:01:07 +01:00
|
|
|
#include "../io/misc.h"
|
2018-09-29 20:52:13 +02:00
|
|
|
#include "../io/nativefilestream.h"
|
2018-10-03 21:26:00 +02:00
|
|
|
#include "../io/path.h"
|
2016-02-06 02:52:06 +01:00
|
|
|
|
2018-08-12 22:17:09 +02:00
|
|
|
#include <cerrno>
|
2016-02-06 02:52:06 +01:00
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <fstream>
|
2016-07-30 22:34:31 +02:00
|
|
|
#include <initializer_list>
|
2017-05-01 03:13:11 +02:00
|
|
|
#include <iostream>
|
2017-05-19 00:12:07 +02:00
|
|
|
#include <limits>
|
2016-02-06 02:52:06 +01:00
|
|
|
|
2016-07-30 22:34:31 +02:00
|
|
|
#ifdef PLATFORM_UNIX
|
2017-05-01 03:13:11 +02:00
|
|
|
#include <poll.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
2016-07-30 22:34:31 +02:00
|
|
|
#endif
|
2016-02-09 02:21:42 +01:00
|
|
|
|
2018-09-29 20:52:13 +02:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
#include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
2016-02-06 02:52:06 +01:00
|
|
|
using namespace std;
|
|
|
|
using namespace ApplicationUtilities;
|
2016-02-17 20:21:11 +01:00
|
|
|
using namespace ConversionUtilities;
|
2017-10-24 01:02:07 +02:00
|
|
|
using namespace EscapeCodes;
|
2016-06-14 22:53:19 +02:00
|
|
|
using namespace IoUtilities;
|
2016-02-06 02:52:06 +01:00
|
|
|
|
2016-06-10 22:59:22 +02:00
|
|
|
/*!
|
|
|
|
* \brief Contains classes and functions utilizing creating of test applications.
|
|
|
|
*/
|
2016-02-06 02:52:06 +01:00
|
|
|
namespace TestUtilities {
|
|
|
|
|
2018-09-29 20:52:13 +02:00
|
|
|
bool fileSystemItemExists(const string &path)
|
|
|
|
{
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
|
|
struct stat res;
|
|
|
|
return stat(path.data(), &res) == 0;
|
|
|
|
#else
|
2018-10-07 21:01:05 +02:00
|
|
|
const auto widePath(convertMultiByteToWide(path));
|
|
|
|
if (!widePath.first) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto fileType(GetFileAttributesW(widePath.first.get()));
|
2018-09-29 20:52:13 +02:00
|
|
|
return fileType != INVALID_FILE_ATTRIBUTES;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool fileExists(const string &path)
|
|
|
|
{
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
|
|
struct stat res;
|
|
|
|
return stat(path.data(), &res) == 0 && !S_ISDIR(res.st_mode);
|
|
|
|
#else
|
2018-10-07 21:01:05 +02:00
|
|
|
const auto widePath(convertMultiByteToWide(path));
|
|
|
|
if (!widePath.first) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto fileType(GetFileAttributesW(widePath.first.get()));
|
2018-10-03 22:15:08 +02:00
|
|
|
return (fileType != INVALID_FILE_ATTRIBUTES) && !(fileType & FILE_ATTRIBUTE_DIRECTORY) && !(fileType & FILE_ATTRIBUTE_DEVICE);
|
2018-09-29 20:52:13 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool dirExists(const string &path)
|
|
|
|
{
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
|
|
struct stat res;
|
|
|
|
return stat(path.data(), &res) == 0 && S_ISDIR(res.st_mode);
|
|
|
|
#else
|
2018-10-07 21:01:05 +02:00
|
|
|
const auto widePath(convertMultiByteToWide(path));
|
|
|
|
if (!widePath.first) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto fileType(GetFileAttributesW(widePath.first.get()));
|
2018-10-03 22:15:08 +02:00
|
|
|
return (fileType != INVALID_FILE_ATTRIBUTES) && (fileType & FILE_ATTRIBUTE_DIRECTORY);
|
2018-09-29 20:52:13 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool makeDir(const string &path)
|
|
|
|
{
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
|
|
return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
|
|
|
|
#else
|
2018-10-07 21:01:05 +02:00
|
|
|
const auto widePath(convertMultiByteToWide(path));
|
|
|
|
if (!widePath.first) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return CreateDirectoryW(widePath.first.get(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS;
|
2018-09-29 20:52:13 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2016-02-06 02:52:06 +01:00
|
|
|
TestApplication *TestApplication::m_instance = nullptr;
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class TestApplication
|
2016-02-09 02:21:42 +01:00
|
|
|
* \brief The TestApplication class simplifies writing test applications that require opening test files.
|
2016-02-06 02:52:06 +01:00
|
|
|
* \remarks Only one instance is allowed at a time (singletone class).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a TestApplication instance.
|
|
|
|
* \throws Throws std::runtime_error if an instance has already been created.
|
|
|
|
*/
|
2017-05-01 03:13:11 +02:00
|
|
|
TestApplication::TestApplication(int argc, char **argv)
|
|
|
|
: m_helpArg(m_parser)
|
|
|
|
, m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files")
|
|
|
|
, m_applicationPathArg("app-path", 'a', "specifies the path of the application to be tested")
|
|
|
|
, m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files")
|
|
|
|
, m_unitsArg("units", 'u', "specifies the units to test; omit to test all units")
|
2016-02-06 02:52:06 +01:00
|
|
|
{
|
2016-02-09 02:21:42 +01:00
|
|
|
// check whether there is already an instance
|
2017-05-01 03:13:11 +02:00
|
|
|
if (m_instance) {
|
2016-02-06 02:52:06 +01:00
|
|
|
throw runtime_error("only one TestApplication instance allowed at a time");
|
|
|
|
}
|
|
|
|
m_instance = this;
|
2016-02-09 02:21:42 +01:00
|
|
|
|
2017-10-30 23:01:07 +01:00
|
|
|
// determine fallback path for testfiles which is used when --test-files-path/-p not present
|
|
|
|
// -> read TEST_FILE_PATH environment variable
|
2018-02-03 17:08:43 +01:00
|
|
|
m_fallbackTestFilesPath = readTestfilePathFromEnv();
|
2017-10-30 23:01:07 +01:00
|
|
|
// -> find source directory if TEST_FILE_PATH not present
|
2018-02-03 17:08:43 +01:00
|
|
|
bool fallbackIsSourceDir = m_fallbackTestFilesPath.empty();
|
2017-10-30 23:01:07 +01:00
|
|
|
if (fallbackIsSourceDir) {
|
2018-02-03 17:08:43 +01:00
|
|
|
m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
|
2016-02-06 02:52:06 +01:00
|
|
|
}
|
2016-02-09 02:21:42 +01:00
|
|
|
|
2017-11-12 16:17:08 +01:00
|
|
|
// handle specified arguments (if present)
|
|
|
|
if (argc && argv) {
|
|
|
|
// setup argument parser
|
|
|
|
for (Argument *arg : initializer_list<Argument *>{ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg }) {
|
|
|
|
arg->setRequiredValueCount(1);
|
|
|
|
arg->setValueNames({ "path" });
|
|
|
|
arg->setCombinable(true);
|
|
|
|
}
|
|
|
|
m_unitsArg.setRequiredValueCount(Argument::varValueCount);
|
|
|
|
m_unitsArg.setValueNames({ "unit1", "unit2", "unit3" });
|
|
|
|
m_unitsArg.setCombinable(true);
|
|
|
|
m_parser.setMainArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg, &m_helpArg });
|
2016-02-09 02:21:42 +01:00
|
|
|
|
2017-11-12 16:17:08 +01:00
|
|
|
// parse arguments
|
|
|
|
try {
|
|
|
|
m_parser.parseArgs(argc, argv);
|
|
|
|
} catch (const Failure &failure) {
|
|
|
|
cerr << failure;
|
|
|
|
m_valid = false;
|
|
|
|
return;
|
|
|
|
}
|
2017-06-20 23:19:49 +02:00
|
|
|
|
2017-11-12 16:17:08 +01:00
|
|
|
// print help
|
|
|
|
if (m_helpArg.isPresent()) {
|
|
|
|
exit(0);
|
|
|
|
}
|
2017-10-24 01:02:07 +02:00
|
|
|
}
|
2017-06-20 23:19:49 +02:00
|
|
|
|
2017-10-24 01:02:07 +02:00
|
|
|
// handle path for testfiles and working-copy
|
|
|
|
cerr << "Directories used to search for testfiles:" << endl;
|
|
|
|
if (m_testFilesPathArg.isPresent()) {
|
|
|
|
if (*m_testFilesPathArg.values().front()) {
|
2017-10-30 23:01:07 +01:00
|
|
|
cerr << ((m_testFilesPath = m_testFilesPathArg.values().front()) += '/') << endl;
|
2017-10-24 01:02:07 +02:00
|
|
|
} else {
|
2017-10-30 23:01:07 +01:00
|
|
|
cerr << (m_testFilesPath = "./") << endl;
|
2016-02-06 02:52:06 +01:00
|
|
|
}
|
2018-02-03 17:08:43 +01:00
|
|
|
} else {
|
|
|
|
// use fallback path if --test-files-path/-p not present
|
|
|
|
m_testFilesPath.swap(m_fallbackTestFilesPath);
|
2018-11-02 22:36:22 +01:00
|
|
|
cerr << m_testFilesPath << endl;
|
2018-02-03 17:08:43 +01:00
|
|
|
}
|
|
|
|
// if it wasn't already the case, use the source directory as fallback dir
|
|
|
|
if (m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
|
|
|
|
m_fallbackTestFilesPath = readTestfilePathFromSrcRef();
|
|
|
|
fallbackIsSourceDir = true;
|
2017-10-24 01:02:07 +02:00
|
|
|
}
|
2017-10-30 23:01:07 +01:00
|
|
|
if (!m_fallbackTestFilesPath.empty() && m_testFilesPath != m_fallbackTestFilesPath) {
|
|
|
|
cerr << m_fallbackTestFilesPath << endl;
|
2017-10-24 01:02:07 +02:00
|
|
|
}
|
|
|
|
cerr << "./testfiles/" << endl << endl;
|
|
|
|
cerr << "Directory used to store working copies:" << endl;
|
|
|
|
if (m_workingDirArg.isPresent()) {
|
|
|
|
if (*m_workingDirArg.values().front()) {
|
|
|
|
(m_workingDir = m_workingDirArg.values().front()) += '/';
|
2016-02-09 02:21:42 +01:00
|
|
|
} else {
|
2017-10-24 01:02:07 +02:00
|
|
|
m_workingDir = "./";
|
2016-02-09 02:21:42 +01:00
|
|
|
}
|
2017-10-24 01:02:07 +02:00
|
|
|
} else if (const char *workingDirEnv = getenv("WORKING_DIR")) {
|
2018-02-03 17:08:43 +01:00
|
|
|
if (*workingDirEnv) {
|
|
|
|
m_workingDir = argsToString(workingDirEnv, '/');
|
2017-06-20 23:19:49 +02:00
|
|
|
}
|
2017-10-24 01:02:07 +02:00
|
|
|
} else {
|
|
|
|
if (m_testFilesPathArg.isPresent()) {
|
2017-10-30 23:01:07 +01:00
|
|
|
m_workingDir = m_testFilesPath + "workingdir/";
|
|
|
|
} else if (!m_fallbackTestFilesPath.empty() && !fallbackIsSourceDir) {
|
|
|
|
m_workingDir = m_fallbackTestFilesPath + "workingdir/";
|
2017-10-24 01:02:07 +02:00
|
|
|
} else {
|
|
|
|
m_workingDir = "./testfiles/workingdir/";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cerr << m_workingDir << endl << endl;
|
2017-06-20 23:19:49 +02:00
|
|
|
|
2017-10-24 01:02:07 +02:00
|
|
|
// clear list of all additional profiling files created when forking the test application
|
|
|
|
if (const char *profrawListFile = getenv("LLVM_PROFILE_LIST_FILE")) {
|
|
|
|
ofstream(profrawListFile, ios_base::trunc);
|
2016-02-06 02:52:06 +01:00
|
|
|
}
|
2017-10-24 01:02:07 +02:00
|
|
|
|
|
|
|
m_valid = true;
|
|
|
|
cerr << TextAttribute::Bold << "Executing test cases ..." << Phrases::EndFlush;
|
2016-02-06 02:52:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Destroys the TestApplication.
|
|
|
|
*/
|
|
|
|
TestApplication::~TestApplication()
|
|
|
|
{
|
|
|
|
m_instance = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-02-09 02:21:42 +01:00
|
|
|
* \brief Returns the full path of the test file with the specified \a name.
|
2017-11-12 16:58:53 +01:00
|
|
|
*
|
|
|
|
* The specified \a name might be a relative path in the testfiles directory.
|
|
|
|
*
|
|
|
|
* The following directories are searched for the specified testfile:
|
|
|
|
* 1. The directory specified as CLI argument.
|
2018-02-03 17:08:43 +01:00
|
|
|
* 2. The fallback directory, which can be set by setting the environment
|
2017-11-12 16:58:53 +01:00
|
|
|
* variable `TEST_FILE_PATH`.
|
2018-02-03 17:08:43 +01:00
|
|
|
* 3. The source directory, if it could be determined via "srcref"-file
|
|
|
|
* unless both, the CLI argument and environment variable are present.
|
2016-02-06 02:52:06 +01:00
|
|
|
*/
|
|
|
|
string TestApplication::testFilePath(const string &name) const
|
|
|
|
{
|
|
|
|
string path;
|
2016-02-09 02:21:42 +01:00
|
|
|
fstream file; // used to check whether the file is present
|
|
|
|
|
2018-02-03 17:08:43 +01:00
|
|
|
// check the path specified by command line argument or via environment variable
|
|
|
|
if (!m_testFilesPath.empty()) {
|
2018-09-29 20:52:13 +02:00
|
|
|
if (fileExists(path = m_testFilesPath + name)) {
|
2016-02-06 02:52:06 +01:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
2016-02-09 02:21:42 +01:00
|
|
|
|
2018-02-03 17:08:43 +01:00
|
|
|
// check the fallback path (value from environment variable or source directory)
|
2017-10-30 23:01:07 +01:00
|
|
|
if (!m_fallbackTestFilesPath.empty()) {
|
2018-09-29 20:52:13 +02:00
|
|
|
if (fileExists(path = m_fallbackTestFilesPath + name)) {
|
2016-02-06 02:52:06 +01:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
2016-02-09 02:21:42 +01:00
|
|
|
|
|
|
|
// file still not found -> return default path
|
2018-09-29 20:52:13 +02:00
|
|
|
if (!fileExists(path = "./testfiles/" + name)) {
|
2018-10-03 22:15:08 +02:00
|
|
|
cerr << Phrases::Warning << "The testfile \"" << name << "\" can not be located." << Phrases::EndFlush;
|
2017-03-12 20:25:06 +01:00
|
|
|
}
|
|
|
|
return path;
|
2016-02-06 02:52:06 +01:00
|
|
|
}
|
|
|
|
|
2016-02-09 02:21:42 +01:00
|
|
|
/*!
|
|
|
|
* \brief Returns the full path to a working copy of the test file with the specified \a name.
|
2017-11-12 16:58:53 +01:00
|
|
|
*
|
|
|
|
* The specified \a mode controls whether a working copy is actually created or whether just the path is returned.
|
|
|
|
* The test file is located using testFilePath().
|
|
|
|
*
|
2016-07-30 22:34:31 +02:00
|
|
|
* \remarks Currently only available under UNIX.
|
2016-02-09 02:21:42 +01:00
|
|
|
*/
|
2017-02-04 20:16:50 +01:00
|
|
|
string TestApplication::workingCopyPathMode(const string &name, WorkingCopyMode mode) const
|
2016-02-09 02:21:42 +01:00
|
|
|
{
|
|
|
|
// ensure working directory is present
|
2018-09-29 20:52:13 +02:00
|
|
|
if (!dirExists(m_workingDir) && !makeDir(m_workingDir)) {
|
2018-10-06 16:18:39 +02:00
|
|
|
cerr << Phrases::Error << "Unable to create working copy for \"" << name << "\": can't create working directory \"" << m_workingDir << "\"."
|
|
|
|
<< Phrases::EndFlush;
|
2018-07-28 20:00:28 +02:00
|
|
|
return string();
|
2016-02-09 02:21:42 +01:00
|
|
|
}
|
|
|
|
|
2016-02-17 20:21:11 +01:00
|
|
|
// ensure subdirectory exists
|
2018-07-28 20:00:28 +02:00
|
|
|
const auto parts = splitString<vector<string>>(name, "/", EmptyPartsTreat::Omit);
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!parts.empty()) {
|
2018-07-28 19:39:11 +02:00
|
|
|
// create subdirectory level by level
|
2018-07-28 20:00:28 +02:00
|
|
|
string currentLevel;
|
|
|
|
currentLevel.reserve(m_workingDir.size() + name.size() + 1);
|
|
|
|
currentLevel.assign(m_workingDir);
|
2017-05-01 03:13:11 +02:00
|
|
|
for (auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
|
|
|
|
if (currentLevel.back() != '/') {
|
2016-04-16 00:50:16 +02:00
|
|
|
currentLevel += '/';
|
|
|
|
}
|
|
|
|
currentLevel += *i;
|
2018-07-28 19:39:11 +02:00
|
|
|
|
2018-10-06 16:18:39 +02:00
|
|
|
// continue if subdirectory level already exists or we can successfully create the directory
|
|
|
|
if (dirExists(currentLevel) || makeDir(currentLevel)) {
|
2018-07-28 19:39:11 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// fail otherwise
|
2018-10-06 16:18:39 +02:00
|
|
|
cerr << Phrases::Error << "Unable to create working copy for \"" << name << "\": can't create directory \"" << currentLevel
|
|
|
|
<< "\" (inside working directory)." << Phrases::EndFlush;
|
2018-07-28 19:39:11 +02:00
|
|
|
return string();
|
2016-02-17 20:21:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-28 19:39:11 +02:00
|
|
|
// just return the path if we don't want to actually create a copy
|
2018-07-28 15:59:31 +02:00
|
|
|
if (mode == WorkingCopyMode::NoCopy) {
|
2017-02-04 20:16:50 +01:00
|
|
|
return m_workingDir + name;
|
2016-02-09 02:21:42 +01:00
|
|
|
}
|
2018-07-28 15:59:31 +02:00
|
|
|
|
2018-07-28 19:39:11 +02:00
|
|
|
// copy the file
|
2018-07-28 15:59:31 +02:00
|
|
|
const auto origFilePath(testFilePath(name));
|
2018-07-28 20:28:15 +02:00
|
|
|
auto workingCopyPath(m_workingDir + name);
|
|
|
|
size_t workingCopyPathAttempt = 0;
|
2018-09-29 20:52:13 +02:00
|
|
|
NativeFileStream origFile, workingCopy;
|
2018-07-28 19:39:11 +02:00
|
|
|
origFile.open(origFilePath, ios_base::in | ios_base::binary);
|
|
|
|
if (origFile.fail()) {
|
2018-07-28 20:00:28 +02:00
|
|
|
cerr << Phrases::Error << "Unable to create working copy for \"" << name << "\": an IO error occurred when opening original file \""
|
|
|
|
<< origFilePath << "\"." << Phrases::EndFlush;
|
2018-08-10 16:36:55 +02:00
|
|
|
cerr << "error: " << strerror(errno) << endl;
|
2018-07-28 19:39:11 +02:00
|
|
|
return string();
|
|
|
|
}
|
|
|
|
workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
|
2018-09-29 20:52:13 +02:00
|
|
|
while (workingCopy.fail() && fileSystemItemExists(workingCopyPath)) {
|
2018-07-28 20:28:15 +02:00
|
|
|
// adjust the working copy path if the target file already exists and can not be truncated
|
2018-08-10 16:36:55 +02:00
|
|
|
workingCopyPath = argsToString(m_workingDir, name, '.', ++workingCopyPathAttempt);
|
2018-07-28 20:28:15 +02:00
|
|
|
workingCopy.clear();
|
|
|
|
workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
|
|
|
|
}
|
2018-07-28 19:39:11 +02:00
|
|
|
if (workingCopy.fail()) {
|
2018-07-28 20:00:28 +02:00
|
|
|
cerr << Phrases::Error << "Unable to create working copy for \"" << name << "\": an IO error occurred when opening target file \""
|
|
|
|
<< workingCopyPath << "\"." << Phrases::EndFlush;
|
2018-08-10 16:36:55 +02:00
|
|
|
cerr << "error: " << strerror(errno) << endl;
|
2018-07-28 19:39:11 +02:00
|
|
|
return string();
|
|
|
|
}
|
|
|
|
workingCopy << origFile.rdbuf();
|
|
|
|
if (!origFile.fail() && !workingCopy.fail()) {
|
2018-07-28 15:59:31 +02:00
|
|
|
return workingCopyPath;
|
2018-07-28 19:39:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
cerr << Phrases::Error << "Unable to create working copy for \"" << name << "\": ";
|
|
|
|
if (origFile.fail()) {
|
2018-07-28 20:00:28 +02:00
|
|
|
cerr << "an IO error occurred when reading original file \"" << origFilePath << "\"";
|
2018-07-28 19:39:11 +02:00
|
|
|
return string();
|
|
|
|
}
|
|
|
|
if (workingCopy.fail()) {
|
|
|
|
if (origFile.fail()) {
|
|
|
|
cerr << " and ";
|
|
|
|
}
|
|
|
|
cerr << " an IO error occurred when writing to target file \"" << workingCopyPath << "\".";
|
2018-07-28 15:59:31 +02:00
|
|
|
}
|
2018-08-10 16:36:55 +02:00
|
|
|
cerr << "error: " << strerror(errno) << endl;
|
2016-02-09 02:21:42 +01:00
|
|
|
return string();
|
|
|
|
}
|
2016-07-30 22:34:31 +02:00
|
|
|
|
2017-11-12 16:58:53 +01:00
|
|
|
/*!
|
|
|
|
* \brief Creates a working copy of the test file with the specified \a name and returns the full path of the created file.
|
|
|
|
*
|
|
|
|
* The test file is located using testFilePath().
|
|
|
|
*
|
|
|
|
* \remarks Currently only available under UNIX.
|
|
|
|
*/
|
2017-02-04 20:16:50 +01:00
|
|
|
string TestApplication::workingCopyPath(const string &name) const
|
|
|
|
{
|
|
|
|
return workingCopyPathMode(name, WorkingCopyMode::CreateCopy);
|
|
|
|
}
|
|
|
|
|
2018-09-29 20:52:13 +02:00
|
|
|
#ifdef PLATFORM_UNIX
|
2016-07-30 22:34:31 +02:00
|
|
|
/*!
|
2017-06-20 23:19:49 +02:00
|
|
|
* \brief Executes an application with the specified \a args.
|
|
|
|
* \remarks Provides internal implementation of execApp() and execHelperApp().
|
2016-07-30 22:34:31 +02:00
|
|
|
*/
|
2017-06-20 23:19:49 +02:00
|
|
|
int execAppInternal(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout,
|
|
|
|
const std::string &newProfilingPath)
|
2016-07-30 22:34:31 +02:00
|
|
|
{
|
2016-08-05 01:43:46 +02:00
|
|
|
// print log message
|
2017-05-01 03:13:11 +02:00
|
|
|
if (!suppressLogging) {
|
2018-10-25 18:20:14 +02:00
|
|
|
// print actual appPath and skip first argument instead
|
|
|
|
cout << '-' << ' ' << appPath;
|
|
|
|
if (*args) {
|
|
|
|
for (const char *const *i = args + 1; *i; ++i) {
|
|
|
|
cout << ' ' << *i;
|
|
|
|
}
|
2016-08-05 01:43:46 +02:00
|
|
|
}
|
|
|
|
cout << endl;
|
|
|
|
}
|
2017-06-20 23:19:49 +02:00
|
|
|
|
2016-07-30 22:34:31 +02:00
|
|
|
// create pipes
|
|
|
|
int coutPipes[2], cerrPipes[2];
|
2018-07-28 20:00:28 +02:00
|
|
|
pipe(coutPipes);
|
|
|
|
pipe(cerrPipes);
|
|
|
|
const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
|
|
|
|
const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
|
2017-06-20 23:19:49 +02:00
|
|
|
|
2016-07-30 22:34:31 +02:00
|
|
|
// create child process
|
2018-07-28 20:00:28 +02:00
|
|
|
if (const auto child = fork()) {
|
2016-07-30 22:34:31 +02:00
|
|
|
// parent process: read stdout and stderr from child
|
2018-07-28 20:00:28 +02:00
|
|
|
close(writeCoutPipe);
|
|
|
|
close(writeCerrPipe);
|
2016-08-15 22:35:37 +02:00
|
|
|
|
|
|
|
try {
|
2017-05-01 03:13:11 +02:00
|
|
|
if (child == -1) {
|
2016-08-15 22:35:37 +02:00
|
|
|
throw runtime_error("Unable to create fork");
|
2016-07-30 22:34:31 +02:00
|
|
|
}
|
2016-08-15 22:35:37 +02:00
|
|
|
|
|
|
|
// init file descriptor set for poll
|
|
|
|
struct pollfd fileDescriptorSet[2];
|
|
|
|
fileDescriptorSet[0].fd = readCoutPipe;
|
|
|
|
fileDescriptorSet[1].fd = readCerrPipe;
|
|
|
|
fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
|
|
|
|
|
|
|
|
// init variables for reading
|
2016-07-30 22:34:31 +02:00
|
|
|
char buffer[512];
|
2018-07-28 20:00:28 +02:00
|
|
|
output.clear();
|
|
|
|
errors.clear();
|
2016-08-15 22:35:37 +02:00
|
|
|
|
|
|
|
// poll as long as at least one pipe is open
|
|
|
|
do {
|
2018-07-28 20:00:28 +02:00
|
|
|
const auto retpoll = poll(fileDescriptorSet, 2, timeout);
|
|
|
|
if (retpoll == 0) {
|
2016-08-15 22:35:37 +02:00
|
|
|
throw runtime_error("Poll time-out");
|
2018-07-28 20:00:28 +02:00
|
|
|
}
|
|
|
|
if (retpoll < 0) {
|
2016-08-15 22:35:37 +02:00
|
|
|
throw runtime_error("Poll failed");
|
|
|
|
}
|
2018-07-28 20:00:28 +02:00
|
|
|
if (fileDescriptorSet[0].revents & POLLIN) {
|
|
|
|
const auto count = read(readCoutPipe, buffer, sizeof(buffer));
|
|
|
|
if (count > 0) {
|
|
|
|
output.append(buffer, static_cast<size_t>(count));
|
|
|
|
}
|
|
|
|
} else if (fileDescriptorSet[0].revents & POLLHUP) {
|
|
|
|
close(readCoutPipe);
|
|
|
|
fileDescriptorSet[0].fd = -1;
|
|
|
|
}
|
|
|
|
if (fileDescriptorSet[1].revents & POLLIN) {
|
|
|
|
const auto count = read(readCerrPipe, buffer, sizeof(buffer));
|
|
|
|
if (count > 0) {
|
|
|
|
errors.append(buffer, static_cast<size_t>(count));
|
|
|
|
}
|
|
|
|
} else if (fileDescriptorSet[1].revents & POLLHUP) {
|
|
|
|
close(readCerrPipe);
|
|
|
|
fileDescriptorSet[1].fd = -1;
|
|
|
|
}
|
2017-05-01 03:13:11 +02:00
|
|
|
} while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
|
|
|
|
} catch (...) {
|
2018-07-28 20:00:28 +02:00
|
|
|
// ensure all pipes are closed in the error case
|
|
|
|
close(readCoutPipe);
|
|
|
|
close(readCerrPipe);
|
2016-08-15 22:35:37 +02:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get return code
|
2016-07-30 22:34:31 +02:00
|
|
|
int childReturnCode;
|
|
|
|
waitpid(child, &childReturnCode, 0);
|
|
|
|
return childReturnCode;
|
|
|
|
} else {
|
2017-06-20 23:19:49 +02:00
|
|
|
// child process
|
|
|
|
// -> set pipes to be used for stdout/stderr
|
2018-07-28 20:00:28 +02:00
|
|
|
dup2(writeCoutPipe, STDOUT_FILENO);
|
|
|
|
dup2(writeCerrPipe, STDERR_FILENO);
|
|
|
|
close(readCoutPipe);
|
|
|
|
close(writeCoutPipe);
|
|
|
|
close(readCerrPipe);
|
|
|
|
close(writeCerrPipe);
|
2017-06-20 23:19:49 +02:00
|
|
|
|
|
|
|
// -> modify environment variable LLVM_PROFILE_FILE to apply new path for profiling output
|
|
|
|
if (!newProfilingPath.empty()) {
|
|
|
|
setenv("LLVM_PROFILE_FILE", newProfilingPath.data(), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// -> execute application
|
2016-07-30 22:34:31 +02:00
|
|
|
execv(appPath, const_cast<char *const *>(args));
|
2017-10-24 01:02:07 +02:00
|
|
|
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": execv() failed" << Phrases::EndFlush;
|
2016-07-30 22:34:31 +02:00
|
|
|
exit(-101);
|
|
|
|
}
|
|
|
|
}
|
2017-06-20 23:19:49 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Executes the application to be tested with the specified \a args and stores the standard output and
|
|
|
|
* errors in \a stdout and \a stderr.
|
|
|
|
* \throws Throws std::runtime_error when the application can not be executed.
|
|
|
|
* \remarks
|
|
|
|
* - The specified \a args must be 0 terminated. The first argument is the application name.
|
|
|
|
* - Currently only supported under UNIX.
|
|
|
|
* - \a stdout and \a stderr are cleared before.
|
|
|
|
*/
|
|
|
|
int TestApplication::execApp(const char *const *args, string &output, string &errors, bool suppressLogging, int timeout) const
|
|
|
|
{
|
|
|
|
// increase counter used for giving profiling files unique names
|
|
|
|
static unsigned int invocationCount = 0;
|
|
|
|
++invocationCount;
|
|
|
|
|
2017-10-30 23:03:43 +01:00
|
|
|
// determine the path of the application to be tested
|
|
|
|
const char *appPath = m_applicationPathArg.firstValue();
|
|
|
|
string fallbackAppPath;
|
2017-06-20 23:19:49 +02:00
|
|
|
if (!appPath || !*appPath) {
|
2017-10-30 23:03:43 +01:00
|
|
|
// try to find the path by removing "_tests"-suffix from own executable path
|
|
|
|
// (the own executable path is the path of the test application and its name is usually the name of the application
|
|
|
|
// to be tested with "_tests"-suffix)
|
|
|
|
const char *const testAppPath = m_parser.executable();
|
|
|
|
const size_t testAppPathLength = strlen(testAppPath);
|
|
|
|
if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6, "_tests")) {
|
|
|
|
fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
|
|
|
|
appPath = fallbackAppPath.data();
|
|
|
|
// TODO: it would not hurt to verify whether "fallbackAppPath" actually exists and is executalbe
|
|
|
|
} else {
|
|
|
|
throw runtime_error("Unable to execute application to be tested: no application path specified");
|
|
|
|
}
|
2017-06-20 23:19:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// determine new path for profiling output (to not override profiling output of parent and previous invocations)
|
|
|
|
string newProfilingPath;
|
|
|
|
if (const char *llvmProfileFile = getenv("LLVM_PROFILE_FILE")) {
|
|
|
|
// replace eg. "/some/path/tageditor_tests.profraw" with "/some/path/tageditor0.profraw"
|
|
|
|
if (const char *llvmProfileFileEnd = strstr(llvmProfileFile, ".profraw")) {
|
|
|
|
const string llvmProfileFileWithoutExtension(llvmProfileFile, llvmProfileFileEnd);
|
|
|
|
// extract application name from path
|
|
|
|
const char *appName = strrchr(appPath, '/');
|
|
|
|
appName = appName ? appName + 1 : appPath;
|
|
|
|
// concat new path
|
|
|
|
newProfilingPath = argsToString(llvmProfileFileWithoutExtension, '_', appName, invocationCount, ".profraw");
|
|
|
|
// append path to profiling list file
|
|
|
|
if (const char *profrawListFile = getenv("LLVM_PROFILE_LIST_FILE")) {
|
|
|
|
ofstream(profrawListFile, ios_base::app) << newProfilingPath << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return execAppInternal(appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
|
|
|
|
}
|
|
|
|
|
2017-11-01 19:30:55 +01:00
|
|
|
/*!
|
|
|
|
* \brief Executes an application with the specified \a args.
|
|
|
|
* \remarks
|
|
|
|
* - Intended to invoke helper applications (eg. to setup test files). Use execApp() and TestApplication::execApp() to
|
|
|
|
* invoke the application to be tested itself.
|
|
|
|
* - Currently only supported under UNIX.
|
|
|
|
*/
|
|
|
|
int execHelperApp(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
|
|
|
|
{
|
|
|
|
return execAppInternal(appPath, args, output, errors, suppressLogging, timeout, string());
|
|
|
|
}
|
|
|
|
#endif // PLATFORM_UNIX
|
|
|
|
|
2018-02-03 17:08:43 +01:00
|
|
|
string TestApplication::readTestfilePathFromEnv()
|
2017-10-30 23:01:07 +01:00
|
|
|
{
|
2018-02-03 17:08:43 +01:00
|
|
|
const char *const testFilesPathEnv = getenv("TEST_FILE_PATH");
|
|
|
|
if (!testFilesPathEnv || !*testFilesPathEnv) {
|
|
|
|
return string();
|
2017-10-30 23:01:07 +01:00
|
|
|
}
|
2018-02-03 17:08:43 +01:00
|
|
|
return argsToString(testFilesPathEnv, '/');
|
2017-10-30 23:01:07 +01:00
|
|
|
}
|
|
|
|
|
2018-02-03 17:08:43 +01:00
|
|
|
string TestApplication::readTestfilePathFromSrcRef()
|
2017-10-30 23:01:07 +01:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
// read "srcdirref" file which should contain the path of the source directory; this file should have been
|
|
|
|
// create by the CMake module "TestTarget.cmake"
|
2018-07-28 20:00:28 +02:00
|
|
|
auto srcDirContent(readFile("srcdirref", 2 * 1024));
|
2017-10-30 23:01:07 +01:00
|
|
|
if (srcDirContent.empty()) {
|
|
|
|
cerr << Phrases::Warning << "The file \"srcdirref\" is empty." << Phrases::EndFlush;
|
2018-02-03 17:08:43 +01:00
|
|
|
return string();
|
2017-10-30 23:01:07 +01:00
|
|
|
}
|
|
|
|
|
2018-03-24 17:00:30 +01:00
|
|
|
// check whether the referenced source directory contains a "testfiles" directory
|
2017-10-30 23:01:07 +01:00
|
|
|
#ifdef PLATFORM_UNIX // directoryEntries() is not implemented under Windows so we can only to the check under UNIX
|
|
|
|
bool hasTestfilesDir = false;
|
2017-11-01 19:17:57 +01:00
|
|
|
for (const string &dir : directoryEntries(srcDirContent.data(), DirectoryEntryType::Directory)) {
|
2017-10-30 23:01:07 +01:00
|
|
|
if (dir == "testfiles") {
|
|
|
|
hasTestfilesDir = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!hasTestfilesDir) {
|
|
|
|
cerr << Phrases::Warning
|
|
|
|
<< "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
|
|
|
|
<< Phrases::End << "Referenced source directory: " << srcDirContent << endl;
|
2018-02-03 17:08:43 +01:00
|
|
|
return string();
|
2017-10-30 23:01:07 +01:00
|
|
|
}
|
2017-11-01 19:30:55 +01:00
|
|
|
#endif // PLATFORM_UNIX
|
2017-10-30 23:01:07 +01:00
|
|
|
|
2018-02-03 17:08:43 +01:00
|
|
|
return srcDirContent += "/testfiles/";
|
2017-10-30 23:01:07 +01:00
|
|
|
} catch (...) {
|
2018-05-01 23:59:28 +02:00
|
|
|
cerr << Phrases::Warning << "The file \"srcdirref\" can not be opened. It likely just doesn't exist in the working directory."
|
|
|
|
<< Phrases::EndFlush;
|
2017-10-30 23:01:07 +01:00
|
|
|
catchIoFailure();
|
|
|
|
}
|
2018-02-03 17:08:43 +01:00
|
|
|
return string();
|
2017-10-30 23:01:07 +01:00
|
|
|
}
|
2017-09-17 21:45:23 +02:00
|
|
|
} // namespace TestUtilities
|