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