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