C++ Utilities 5.30.0
Useful C++ classes and routines such as argument parser, IO and conversion utilities
Loading...
Searching...
No Matches
testutils.cpp
Go to the documentation of this file.
1#include "./testutils.h"
2
6#include "../io/misc.h"
8#include "../io/path.h"
10
11#include <cerrno>
12#include <cstdio>
13#include <cstdlib>
14#include <cstring>
15#include <fstream>
16#include <initializer_list>
17#include <iostream>
18#include <limits>
19
20#ifdef PLATFORM_UNIX
21#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
22#include <filesystem>
23#endif
24#include <poll.h>
25#include <sys/stat.h>
26#include <sys/wait.h>
27#include <unistd.h>
28#endif
29
30#ifdef CPP_UTILITIES_BOOST_PROCESS
31#include <boost/asio/buffers_iterator.hpp>
32#include <boost/asio/io_context.hpp>
33#include <boost/asio/streambuf.hpp>
34#if BOOST_VERSION >= 108600
35#include <boost/process/v1/async.hpp>
36#include <boost/process/v1/child.hpp>
37#include <boost/process/v1/env.hpp>
38#include <boost/process/v1/environment.hpp>
39#include <boost/process/v1/group.hpp>
40#include <boost/process/v1/io.hpp>
41#include <boost/process/v1/search_path.hpp>
42#else
43#include <boost/process/async.hpp>
44#include <boost/process/child.hpp>
45#include <boost/process/env.hpp>
46#include <boost/process/environment.hpp>
47#include <boost/process/group.hpp>
48#include <boost/process/io.hpp>
49#include <boost/process/search_path.hpp>
50namespace boost::process {
51namespace v1 = boost::process;
52}
53#endif
54#endif
55
56#ifdef PLATFORM_WINDOWS
57#include <windows.h>
58#endif
59
60using namespace std;
61using namespace CppUtilities::EscapeCodes;
62
66namespace CppUtilities {
67
69static bool fileSystemItemExists(const string &path)
70{
71#ifdef PLATFORM_UNIX
72 struct stat res;
73 return stat(path.data(), &res) == 0;
74#else
75 const auto widePath(convertMultiByteToWide(path));
76 if (!widePath.first) {
77 return false;
78 }
79 const auto fileType(GetFileAttributesW(widePath.first.get()));
80 return fileType != INVALID_FILE_ATTRIBUTES;
81#endif
82}
83
84static bool fileExists(const string &path)
85{
86#ifdef PLATFORM_UNIX
87 struct stat res;
88 return stat(path.data(), &res) == 0 && !S_ISDIR(res.st_mode);
89#else
90 const auto widePath(convertMultiByteToWide(path));
91 if (!widePath.first) {
92 return false;
93 }
94 const auto fileType(GetFileAttributesW(widePath.first.get()));
95 return (fileType != INVALID_FILE_ATTRIBUTES) && !(fileType & FILE_ATTRIBUTE_DIRECTORY) && !(fileType & FILE_ATTRIBUTE_DEVICE);
96#endif
97}
98
99static bool dirExists(const string &path)
100{
101#ifdef PLATFORM_UNIX
102 struct stat res;
103 return stat(path.data(), &res) == 0 && S_ISDIR(res.st_mode);
104#else
105 const auto widePath(convertMultiByteToWide(path));
106 if (!widePath.first) {
107 return false;
108 }
109 const auto fileType(GetFileAttributesW(widePath.first.get()));
110 return (fileType != INVALID_FILE_ATTRIBUTES) && (fileType & FILE_ATTRIBUTE_DIRECTORY);
111#endif
112}
113
114static bool makeDir(const string &path)
115{
116#ifdef PLATFORM_UNIX
117 return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
118#else
119 const auto widePath(convertMultiByteToWide(path));
120 if (!widePath.first) {
121 return false;
122 }
123 return CreateDirectoryW(widePath.first.get(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS;
124#endif
125}
127
128TestApplication *TestApplication::s_instance = nullptr;
129
135
146
151TestApplication::TestApplication(int argc, const char *const *argv)
152 : m_listArg("list", 'l', "lists available test units")
153 , m_runArg("run", 'r', "runs the tests")
154 , m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files", { "path" })
155 , m_applicationPathArg("app-path", 'a', "specifies the path of the application to be tested", { "path" })
156 , m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files", { "path" })
157 , m_unitsArg("units", 'u', "specifies the units to test; omit to test all units", { "unit1", "unit2", "unit3" })
158{
159 // check whether there is already an instance
160 if (s_instance) {
161 throw runtime_error("only one TestApplication instance allowed at a time");
162 }
163 s_instance = this;
164
165 // handle specified arguments (if present)
166 if (argc && argv) {
167 // setup argument parser
168 m_testFilesPathArg.setRequiredValueCount(Argument::varValueCount);
169 m_unitsArg.setRequiredValueCount(Argument::varValueCount);
170 m_runArg.setImplicit(true);
171 m_runArg.setSubArguments({ &m_testFilesPathArg, &m_applicationPathArg, &m_workingDirArg, &m_unitsArg });
172 m_parser.setMainArguments({ &m_runArg, &m_listArg, &m_parser.noColorArg(), &m_parser.helpArg() });
173
174 // parse arguments
175 try {
177 } catch (const ParseError &failure) {
178 cerr << failure;
179 m_valid = false;
180 return;
181 }
182
183 // print help
184 if (m_parser.helpArg().isPresent()) {
185 exit(0);
186 }
187 }
188
189 // set paths for testfiles
190 // -> set paths set via CLI argument
191 if (m_testFilesPathArg.isPresent()) {
192 for (const char *const testFilesPath : m_testFilesPathArg.values()) {
193 if (*testFilesPath) {
194 m_testFilesPaths.emplace_back(argsToString(testFilesPath, '/'));
195 } else {
196 m_testFilesPaths.emplace_back("./");
197 }
198 }
199 }
200 // -> read TEST_FILE_PATH environment variable
201 bool hasTestFilePathFromEnv;
202 if (auto testFilePathFromEnv = readTestfilePathFromEnv(); (hasTestFilePathFromEnv = !testFilePathFromEnv.empty())) {
203 m_testFilesPaths.emplace_back(std::move(testFilePathFromEnv));
204 }
205 // -> find source directory
206 if (auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
207 m_testFilesPaths.insert(m_testFilesPaths.end(), std::make_move_iterator(testFilePathFromSrcDirRef.begin()),
208 std::make_move_iterator(testFilePathFromSrcDirRef.end()));
209 }
210 // -> try testfiles directory in working directory
211 m_testFilesPaths.emplace_back("./testfiles/");
212 for (const auto &testFilesPath : m_testFilesPaths) {
213 cerr << testFilesPath << '\n';
214 }
215
216 // set path for working-copy
217 if (m_workingDirArg.isPresent()) {
218 if (*m_workingDirArg.values().front()) {
219 (m_workingDir = m_workingDirArg.values().front()) += '/';
220 } else {
221 m_workingDir = "./";
222 }
223 } else if (const char *const workingDirEnv = getenv("WORKING_DIR")) {
224 if (*workingDirEnv) {
225 m_workingDir = argsToString(workingDirEnv, '/');
226 }
227 } else {
228 if ((m_testFilesPathArg.isPresent() && !m_testFilesPathArg.values().empty()) || hasTestFilePathFromEnv) {
229 m_workingDir = m_testFilesPaths.front() + "workingdir/";
230 } else {
231 m_workingDir = "./testfiles/workingdir/";
232 }
233 }
234 cerr << "Directory used to store working copies:\n" << m_workingDir << '\n';
235
236 // clear list of all additional profiling files created when forking the test application
237 if (const char *const profrawListFile = getenv("LLVM_PROFILE_LIST_FILE")) {
238 ofstream(profrawListFile, ios_base::trunc);
239 }
240
241 m_valid = true;
242}
243
248{
249 s_instance = nullptr;
250}
251
264std::string TestApplication::testFilePath(const std::string &relativeTestFilePath) const
265{
266 std::string path;
267 for (const auto &testFilesPath : m_testFilesPaths) {
268 if (fileExists(path = testFilesPath + relativeTestFilePath)) {
269 return path;
270 }
271 }
272 throw std::runtime_error("The test file \"" % relativeTestFilePath % "\" can not be located. Was looking under:\n"
273 + joinStrings(m_testFilesPaths, "\n", false, " - ", relativeTestFilePath));
274}
275
282std::string TestApplication::testDirPath(const std::string &relativeTestDirPath) const
283{
284 std::string path;
285 for (const auto &testFilesPath : m_testFilesPaths) {
286 if (dirExists(path = testFilesPath + relativeTestDirPath)) {
287 return path;
288 }
289 }
290 throw std::runtime_error("The test directory \"" % relativeTestDirPath % "\" can not be located. Was looking under:\n"
291 + joinStrings(m_testFilesPaths, "\n", false, " - ", relativeTestDirPath));
292}
293
301string TestApplication::workingCopyPath(const string &relativeTestFilePath, WorkingCopyMode mode) const
302{
303 return workingCopyPathAs(relativeTestFilePath, relativeTestFilePath, mode);
304}
305
321 const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath, WorkingCopyMode mode) const
322{
323 // ensure working directory is present
324 auto workingCopyPath = std::string();
325 if (!dirExists(m_workingDir) && !makeDir(m_workingDir)) {
326 cerr << Phrases::Error << "Unable to create working copy for \"" << relativeTestFilePath << "\": can't create working directory \""
327 << m_workingDir << "\"." << Phrases::EndFlush;
328 return workingCopyPath;
329 }
330
331 // ensure subdirectory exists
332 const auto parts = splitString<vector<string>>(relativeWorkingCopyPath, "/", EmptyPartsTreat::Omit);
333 if (!parts.empty()) {
334 // create subdirectory level by level
335 string currentLevel;
336 currentLevel.reserve(m_workingDir.size() + relativeWorkingCopyPath.size() + 1);
337 currentLevel.assign(m_workingDir);
338 for (auto i = parts.cbegin(), end = parts.end() - 1; i != end; ++i) {
339 if (currentLevel.back() != '/') {
340 currentLevel += '/';
341 }
342 currentLevel += *i;
343
344 // continue if subdirectory level already exists or we can successfully create the directory
345 if (dirExists(currentLevel) || makeDir(currentLevel)) {
346 continue;
347 }
348 // fail otherwise
349 cerr << Phrases::Error << "Unable to create working copy for \"" << relativeWorkingCopyPath << "\": can't create directory \""
350 << currentLevel << "\" (inside working directory)." << Phrases::EndFlush;
351 return workingCopyPath;
352 }
353 }
354
355 workingCopyPath = m_workingDir + relativeWorkingCopyPath;
356 switch (mode) {
358 // just return the path if we don't want to actually create a copy
359 return workingCopyPath;
361 // ensure the file does not exist in cleanup mode
362 if (std::remove(workingCopyPath.data()) != 0 && errno != ENOENT) {
363 const auto error = std::strerror(errno);
364 cerr << Phrases::Error << "Unable to delete \"" << workingCopyPath << "\": " << error << Phrases::EndFlush;
365 workingCopyPath.clear();
366 }
367 return workingCopyPath;
368 default:;
369 }
370
371 // copy the file
372 const auto origFilePath = testFilePath(relativeTestFilePath);
373 size_t workingCopyPathAttempt = 0;
374 NativeFileStream origFile, workingCopy;
375 origFile.open(origFilePath, ios_base::in | ios_base::binary);
376 if (origFile.fail()) {
377 cerr << Phrases::Error << "Unable to create working copy for \"" << relativeTestFilePath
378 << "\": an IO error occurred when opening original file \"" << origFilePath << "\"." << Phrases::EndFlush;
379 cerr << "error: " << std::strerror(errno) << endl;
380 workingCopyPath.clear();
381 return workingCopyPath;
382 }
383 workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
384 while (workingCopy.fail() && fileSystemItemExists(workingCopyPath)) {
385 // adjust the working copy path if the target file already exists and can not be truncated
386 workingCopyPath = argsToString(m_workingDir, relativeWorkingCopyPath, '.', ++workingCopyPathAttempt);
387 workingCopy.clear();
388 workingCopy.open(workingCopyPath, ios_base::out | ios_base::binary | ios_base::trunc);
389 }
390 if (workingCopy.fail()) {
391 cerr << Phrases::Error << "Unable to create working copy for \"" << relativeTestFilePath
392 << "\": an IO error occurred when opening target file \"" << workingCopyPath << "\"." << Phrases::EndFlush;
393 cerr << "error: " << strerror(errno) << endl;
394 workingCopyPath.clear();
395 return workingCopyPath;
396 }
397 workingCopy << origFile.rdbuf();
398 workingCopy.close();
399 if (!origFile.fail() && !workingCopy.fail()) {
400 return workingCopyPath;
401 }
402
403 cerr << Phrases::Error << "Unable to create working copy for \"" << relativeTestFilePath << "\": ";
404 if (origFile.fail()) {
405 cerr << "an IO error occurred when reading original file \"" << origFilePath << "\"";
406 workingCopyPath.clear();
407 return workingCopyPath;
408 }
409 if (workingCopy.fail()) {
410 if (origFile.fail()) {
411 cerr << " and ";
412 }
413 cerr << " an IO error occurred when writing to target file \"" << workingCopyPath << "\".";
414 }
415 cerr << "error: " << strerror(errno) << endl;
416 workingCopyPath.clear();
417 return workingCopyPath;
418}
419
420#ifdef CPP_UTILITIES_HAS_EXEC_APP
421
422#if defined(CPP_UTILITIES_BOOST_PROCESS)
423inline static std::string streambufToString(boost::asio::streambuf &buf)
424{
425 const auto begin = boost::asio::buffers_begin(buf.data());
426 return std::string(begin, begin + static_cast<std::ptrdiff_t>(buf.size()));
427}
428#endif
429
434static int execAppInternal(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout,
435 const std::string &newProfilingPath, bool enableSearchPath = false)
436{
437 // print log message
438 if (!suppressLogging) {
439 // print actual appPath and skip first argument instead
440 cout << '-' << ' ' << appPath;
441 if (*args) {
442 for (const char *const *i = args + 1; *i; ++i) {
443 cout << ' ' << *i;
444 }
445 }
446 cout << endl;
447 }
448
449#if defined(CPP_UTILITIES_BOOST_PROCESS)
450 auto path = enableSearchPath ? boost::process::v1::search_path(appPath) : boost::process::v1::filesystem::path(appPath);
451 auto ctx = boost::asio::io_context();
452 auto group = boost::process::v1::group();
453 auto argsAsVector =
454#if defined(PLATFORM_WINDOWS)
455 std::vector<std::wstring>();
456#else
457 std::vector<std::string>();
458#endif
459 if (*args) {
460 for (const char *const *arg = args + 1; *arg; ++arg) {
461#if defined(PLATFORM_WINDOWS)
462 auto ec = std::error_code();
463 argsAsVector.emplace_back(convertMultiByteToWide(ec, std::string_view(*arg)));
464 if (ec) {
465 throw std::runtime_error(argsToString("unable to convert arg \"", *arg, "\" to wide string"));
466 }
467#else
468 argsAsVector.emplace_back(*arg);
469#endif
470 }
471 }
472 auto outputBuffer = boost::asio::streambuf(), errorBuffer = boost::asio::streambuf();
473 auto env = boost::process::v1::environment(boost::this_process::environment());
474 if (!newProfilingPath.empty()) {
475 env["LLVM_PROFILE_FILE"] = newProfilingPath;
476 }
477 auto child = boost::process::v1::child(
478 ctx, group, path, argsAsVector, env, boost::process::v1::std_out > outputBuffer, boost::process::v1::std_err > errorBuffer);
479 if (timeout > 0) {
480 ctx.run_for(std::chrono::milliseconds(timeout));
481 } else {
482 ctx.run();
483 }
484 output = streambufToString(outputBuffer);
485 errors = streambufToString(errorBuffer);
486 child.wait();
487 group.wait();
488 return child.exit_code();
489
490#elif defined(PLATFORM_UNIX)
491 // create pipes
492 int coutPipes[2], cerrPipes[2];
493 if (pipe(coutPipes) != 0 || pipe(cerrPipes) != 0) {
494 throw std::runtime_error(argsToString("Unable to create pipe: ", std::strerror(errno)));
495 }
496 const auto readCoutPipe = coutPipes[0], writeCoutPipe = coutPipes[1];
497 const auto readCerrPipe = cerrPipes[0], writeCerrPipe = cerrPipes[1];
498
499 // create child process
500 if (const auto child = fork()) {
501 // parent process: read stdout and stderr from child
502 close(writeCoutPipe);
503 close(writeCerrPipe);
504
505 try {
506 if (child == -1) {
507 throw std::runtime_error(argsToString("Unable to create fork: ", std::strerror(errno)));
508 }
509
510 // init file descriptor set for poll
511 struct pollfd fileDescriptorSet[2];
512 fileDescriptorSet[0].fd = readCoutPipe;
513 fileDescriptorSet[1].fd = readCerrPipe;
514 fileDescriptorSet[0].events = fileDescriptorSet[1].events = POLLIN;
515
516 // init variables for reading
517 char buffer[512];
518 output.clear();
519 errors.clear();
520
521 // poll as long as at least one pipe is open
522 do {
523 const auto retpoll = poll(fileDescriptorSet, 2, timeout);
524 if (retpoll == 0) {
525 throw std::runtime_error("Poll timed out");
526 }
527 if (retpoll < 0) {
528 throw std::runtime_error(argsToString("Poll failed: ", std::strerror(errno)));
529 }
530 if (fileDescriptorSet[0].revents & POLLIN) {
531 const auto count = read(readCoutPipe, buffer, sizeof(buffer));
532 if (count > 0) {
533 output.append(buffer, static_cast<size_t>(count));
534 }
535 } else if (fileDescriptorSet[0].revents & POLLHUP) {
536 close(readCoutPipe);
537 fileDescriptorSet[0].fd = -1;
538 }
539 if (fileDescriptorSet[1].revents & POLLIN) {
540 const auto count = read(readCerrPipe, buffer, sizeof(buffer));
541 if (count > 0) {
542 errors.append(buffer, static_cast<size_t>(count));
543 }
544 } else if (fileDescriptorSet[1].revents & POLLHUP) {
545 close(readCerrPipe);
546 fileDescriptorSet[1].fd = -1;
547 }
548 } while (fileDescriptorSet[0].fd >= 0 || fileDescriptorSet[1].fd >= 0);
549 } catch (...) {
550 // ensure all pipes are closed in the error case
551 close(readCoutPipe);
552 close(readCerrPipe);
553 throw;
554 }
555
556 // get return code
557 int childReturnCode;
558 waitpid(child, &childReturnCode, 0);
559 waitpid(-child, nullptr, 0);
560 return childReturnCode;
561 } else {
562 // child process
563 // -> set pipes to be used for stdout/stderr
564 if (dup2(writeCoutPipe, STDOUT_FILENO) == -1 || dup2(writeCerrPipe, STDERR_FILENO) == -1) {
565 std::cerr << Phrases::Error << "Unable to duplicate file descriptor: " << std::strerror(errno) << Phrases::EndFlush;
566 std::exit(EXIT_FAILURE);
567 }
568 close(readCoutPipe);
569 close(writeCoutPipe);
570 close(readCerrPipe);
571 close(writeCerrPipe);
572
573 // -> create process group
574 if (setpgid(0, 0)) {
575 cerr << Phrases::Error << "Unable create process group: " << std::strerror(errno) << Phrases::EndFlush;
576 exit(EXIT_FAILURE);
577 }
578
579 // -> modify environment variable LLVM_PROFILE_FILE to apply new path for profiling output
580 if (!newProfilingPath.empty()) {
581 setenv("LLVM_PROFILE_FILE", newProfilingPath.data(), true);
582 }
583
584 // -> execute application
585 if (enableSearchPath) {
586 execvp(appPath, const_cast<char *const *>(args));
587 } else {
588 execv(appPath, const_cast<char *const *>(args));
589 }
590 cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": " << std::strerror(errno) << Phrases::EndFlush;
591 exit(EXIT_FAILURE);
592 }
593
594#else
595 throw std::runtime_error("lauching test applications is not supported on this platform");
596#endif
597}
598
607int TestApplication::execApp(const char *const *args, string &output, string &errors, bool suppressLogging, int timeout) const
608{
609 // increase counter used for giving profiling files unique names
610 static unsigned int invocationCount = 0;
611 ++invocationCount;
612
613 // determine the path of the application to be tested
614 const char *appPath = m_applicationPathArg.firstValue();
615 auto fallbackAppPath = string();
616 if (!appPath || !*appPath) {
617 // try to find the path by removing "_tests"-suffix from own executable path
618 // (the own executable path is the path of the test application and its name is usually the name of the application
619 // to be tested with "_tests"-suffix)
620 const char *const testAppPath = m_parser.executable();
621 const auto testAppPathLength = strlen(testAppPath);
622 if (testAppPathLength > 6 && !strcmp(testAppPath + testAppPathLength - 6, "_tests")) {
623 fallbackAppPath.assign(testAppPath, testAppPathLength - 6);
624 appPath = fallbackAppPath.data();
625 // TODO: it would not hurt to verify whether "fallbackAppPath" actually exists and is executable
626 } else {
627 throw runtime_error("Unable to execute application to be tested: no application path specified");
628 }
629 }
630
631 // determine new path for profiling output (to not override profiling output of parent and previous invocations)
632 const auto newProfilingPath = [appPath] {
633 auto path = string();
634 const char *const llvmProfileFile = getenv("LLVM_PROFILE_FILE");
635 if (!llvmProfileFile) {
636 return path;
637 }
638 // replace eg. "/some/path/tageditor_tests.profraw" with "/some/path/tageditor0.profraw"
639 const char *const llvmProfileFileEnd = strstr(llvmProfileFile, ".profraw");
640 if (!llvmProfileFileEnd) {
641 return path;
642 }
643 const auto llvmProfileFileWithoutExtension = string(llvmProfileFile, llvmProfileFileEnd);
644 // extract application name from path
645 const char *appName = strrchr(appPath, '/');
646 appName = appName ? appName + 1 : appPath;
647 // concat new path
648 path = argsToString(llvmProfileFileWithoutExtension, '_', appName, invocationCount, ".profraw");
649 // append path to profiling list file
650 if (const char *const profrawListFile = getenv("LLVM_PROFILE_LIST_FILE")) {
651 ofstream(profrawListFile, ios_base::app) << path << endl;
652 }
653 return path;
654 }();
655
656 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout, newProfilingPath);
657}
658
665int execHelperApp(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
666{
667 return execAppInternal(appPath, args, output, errors, suppressLogging, timeout, string());
668}
669
679int execHelperAppInSearchPath(
680 const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
681{
682 return execAppInternal(appName, args, output, errors, suppressLogging, timeout, string(), true);
683}
684#endif
685
689string TestApplication::readTestfilePathFromEnv()
690{
691 const char *const testFilesPathEnv = getenv("TEST_FILE_PATH");
692 if (!testFilesPathEnv || !*testFilesPathEnv) {
693 return string();
694 }
695 return argsToString(testFilesPathEnv, '/');
696}
697
703std::vector<std::string> TestApplication::readTestfilePathFromSrcRef()
704{
705 // find the path of the current executable on platforms supporting "/proc/self/exe"; otherwise assume the current working directory
706 // is the executable path
707 auto res = std::vector<std::string>();
708 auto binaryPath = std::string();
709#if defined(CPP_UTILITIES_USE_STANDARD_FILESYSTEM) && defined(PLATFORM_UNIX)
710 try {
711 binaryPath = std::filesystem::read_symlink("/proc/self/exe").parent_path();
712 binaryPath += '/';
713 } catch (const std::filesystem::filesystem_error &e) {
714 cerr << Phrases::Warning << "Unable to detect binary path for finding \"srcdirref\": " << e.what() << Phrases::EndFlush;
715 }
716#endif
717 const auto srcdirrefPath = binaryPath + "srcdirref";
718 try {
719 // read "srcdirref" file which should contain the path of the source directory
720 const auto srcDirContent = readFile(srcdirrefPath, 2 * 1024);
721 if (srcDirContent.empty()) {
722 cerr << Phrases::Warning << "The file \"srcdirref\" is empty." << Phrases::EndFlush;
723 return res;
724 }
725
726 // check whether the referenced source directories contain a "testfiles" directory
727 const auto srcPaths = splitStringSimple<std::vector<std::string_view>>(srcDirContent, "\n");
728 for (const auto &srcPath : srcPaths) {
729 auto testfilesPath = argsToString(srcPath, "/testfiles/");
730 if (dirExists(testfilesPath)) {
731 res.emplace_back(std::move(testfilesPath));
732 } else {
733 cerr << Phrases::Warning
734 << "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
735 << Phrases::End << "Referenced source directory: " << testfilesPath << endl;
736 }
737 }
738 return res;
739
740 } catch (const std::ios_base::failure &e) {
741 cerr << Phrases::Warning << "The file \"" << srcdirrefPath << "\" can not be opened: " << e.what() << Phrases::EndFlush;
742 }
743 return res;
744}
745} // namespace CppUtilities
static constexpr std::size_t varValueCount
Denotes a variable number of values.
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition parseerror.h:11
The TestApplication class simplifies writing test applications that require opening test files.
Definition testutils.h:34
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.
Definition testutils.h:103
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.
Definition misc.cpp:17
WorkingCopyMode
The WorkingCopyMode enum specifies additional options to influence behavior of TestApplication::worki...
Definition testutils.h:28
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.
std::fstream NativeFileStream
Container splitStringSimple(Detail::StringParamForContainer< Container > string, Detail::StringParamForContainer< Container > delimiter, int maxParts=-1)
Splits the given string (which might also be a string view) at the specified delimiter.
StringType argsToString(Args &&...args)
Container splitString(Detail::StringParamForContainer< Container > string, Detail::StringParamForContainer< Container > delimiter, EmptyPartsTreat emptyPartsRole=EmptyPartsTreat::Keep, int maxParts=-1)
Splits the given string at the specified delimiter.
STL namespace.
constexpr int i