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