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