17 #ifndef COM_BORA_SOFTWARE__BALAU_TESTING__TEST_RUNNER 18 #define COM_BORA_SOFTWARE__BALAU_TESTING__TEST_RUNNER 28 #include <Balau/Testing/Impl/SingleThreadedTestRunnerExecutor.hpp> 29 #include <Balau/Testing/Impl/ProcessPerTestTestRunnerExecutor.hpp> 30 #include <Balau/Testing/Impl/TestGroup.hpp> 31 #include <Balau/Testing/Impl/TestMethodBase.hpp> 32 #include <Balau/Testing/Impl/WorkerProcessesTestRunnerExecutor.hpp> 33 #include <Balau/Testing/Impl/WorkerThreadsTestRunnerExecutor.hpp> 34 #include <Balau/Testing/Reporters/SurefireTestReportGenerator.hpp> 42 #include <boost/predef.h> 46 enum class TestRunnerOption {
55 inline std::string
toString(
const TestRunnerOption & option) {
57 case TestRunnerOption::ExecutionModel:
return "ExecutionModel";
58 case TestRunnerOption::Namespaces:
return "Namespaces";
59 case TestRunnerOption::PauseAtExit:
return "PauseAtExit";
60 case TestRunnerOption::ConcurrencyLevel:
return "ConcurrencyLevel";
61 case TestRunnerOption::ReportFolder:
return "ReportFolder";
62 case TestRunnerOption::Help:
return "Help";
66 Exception::IllegalArgumentException
97 public:
static int run(
int argc,
99 bool ignoreFirst =
true,
103 cl.
parse(argc, argv, ignoreFirst);
105 if (cl.
hasOption(TestRunnerOption::Help)) {
110 if (reportGenerator && cl.
hasOption(TestRunnerOption::ReportFolder)) {
111 const std::string reportFolderString = cl.
getOption(TestRunnerOption::ReportFolder);
112 setReportOutputFolder(
Resource::File(reportFolderString), reportGenerator);
115 const auto executionModel = parseExecutionModel(cl);
118 r.testList = createTestList(cl);
120 r.reportGenerator = std::move(reportGenerator);
121 r.concurrencyLevel = r.getConcurrencyLevel(cl);
122 r.executionModel = r.checkOutOfProcessCapability(executionModel);
123 r.setUseNamespaces(cl.
hasOption(TestRunnerOption::Namespaces));
124 r.pauseAtExit = cl.
hasOption(TestRunnerOption::PauseAtExit);
125 return r.performTestRun();
144 public:
template <
typename ... WriterItemT>
148 std::shared_ptr<Impl::TestReportGenerator> reportGenerator,
149 const WriterItemT & ... writerItems) {
152 cl.
parse(argc, argv, ignoreFirst);
154 if (cl.
hasOption(TestRunnerOption::Help)) {
159 if (reportGenerator && cl.
hasOption(TestRunnerOption::ReportFolder)) {
160 const std::string reportFolderString = cl.
getOption(TestRunnerOption::ReportFolder);
161 setReportOutputFolder(
Resource::File(reportFolderString), reportGenerator);
164 const auto executionModel = parseExecutionModel(cl);
167 r.testList = createTestList(cl);
168 r.writer = Impl::CompositeWriter(writerItems ...);
169 r.reportGenerator = std::move(reportGenerator);
170 r.concurrencyLevel = r.getConcurrencyLevel(cl);
171 r.executionModel = r.checkOutOfProcessCapability(executionModel);
172 r.setUseNamespaces(cl.
hasOption(TestRunnerOption::Namespaces));
173 r.pauseAtExit = cl.
hasOption(TestRunnerOption::PauseAtExit);
174 return r.performTestRun();
193 const bool empty = reportOutputFolder.toRawString().empty();
195 if (reportGenerator && !empty) {
196 setReportOutputFolder(reportOutputFolder, reportGenerator);
204 r.reportGenerator = std::move(reportGenerator);
207 r.concurrencyLevel = r.getConcurrencyLevel();
208 r.executionModel = r.checkOutOfProcessCapability(executionModel);
209 r.setUseNamespaces(
false);
210 r.pauseAtExit =
false;
211 return r.performTestRun();
229 public:
template <
typename ... WriterItemT>
232 std::shared_ptr<Impl::TestReportGenerator> reportGenerator,
233 const WriterItemT & ... writerItems) {
234 const bool empty = reportOutputFolder.
toRawString().empty();
236 if (reportGenerator && !empty) {
237 setReportOutputFolder(reportOutputFolder, reportGenerator);
242 r.writer = Impl::CompositeWriter(writerItems ...);
245 r.reportGenerator = std::move(reportGenerator);
248 r.concurrencyLevel = r.getConcurrencyLevel();
249 r.executionModel = r.checkOutOfProcessCapability(executionModel);
250 r.setUseNamespaces(
false);
251 r.pauseAtExit =
false;
252 return r.performTestRun();
269 .withOption(TestRunnerOption::ExecutionModel,
"e",
"execution-model",
true,
"The execution model (default = SingleThreaded).")
270 .
withOption(TestRunnerOption::Namespaces,
"n",
"namespaces",
false,
"Use namespaces in test group names (default is not to use).")
271 .
withOption(TestRunnerOption::PauseAtExit,
"p",
"pause",
false,
"Pause at exit (default is not to pause).")
272 .
withOption(TestRunnerOption::ConcurrencyLevel,
"c",
"concurrency",
true,
"The number of threads or processes to use to run the tests (default = detect).")
273 .
withOption(TestRunnerOption::ReportFolder,
"r",
"report-folder",
true,
"Generate test reports in the specified folder.")
274 .
withOption(TestRunnerOption::Help,
"h",
"help",
false,
"Displays this help message.")
278 private:
static void setReportOutputFolder(
const Resource::File & reportOutputFolder,
279 std::shared_ptr<Impl::TestReportGenerator> & reportGenerator) {
283 if (path.isRegularFile()) {
286 , ::
toString(
"The specified report output folder is an existing regular file: ", path.toRawString())
290 if (!path.isRegularDirectory()) {
292 path.createDirectories();
293 }
catch (
const boost::filesystem::filesystem_error & e) {
296 , ::
toString(
"The specified report output folder could not be created: ", path.toRawString())
301 reportGenerator->setOutputFolder(path);
314 , concurrencyLevel(getConcurrencyLevel())
316 , useNamespaces(
false)
317 , pauseAtExit(
false) {}
320 private:
unsigned int getGroupIndex()
override {
321 std::lock_guard<std::mutex> lock(runner().mutex);
322 return runner().currentGroupIndex++;
327 if (!cl.
hasOption(TestRunnerOption::ExecutionModel)) {
336 std::cout <<
"\nRunning tests for command line specified execution model " <<
toString(model) <<
"\n" << std::endl;
339 std::cout <<
"\nUnknown execution model specified. Using execution model SingleThreaded\n" << std::endl;
346 private:
int performTestRun() {
349 writer <<
"\n------------------------- STARTING TESTS -------------------------\n\n";
352 std::unique_ptr<Impl::TestRunnerExecutor> executor;
354 switch (executionModel) {
356 writer <<
"Run type = single process, single threaded\n";
358 executor = std::unique_ptr<Impl::TestRunnerExecutor>(
359 new Impl::SingleThreadedTestRunnerExecutor(
360 writer, reportGenerator, useNamespaces, testCasesByGroup, testList
368 writer <<
"Run type = single process, multi-threaded " 369 <<
"(" << concurrencyLevel <<
" thread" << (concurrencyLevel > 1 ?
"s" :
"") <<
")\n";
371 executor = std::unique_ptr<Impl::TestRunnerExecutor>(
372 new Impl::WorkerThreadsTestRunnerExecutor(
373 writer, reportGenerator, useNamespaces, testCasesByGroup, testList, concurrencyLevel
381 writer <<
"Run type = worker processes " 382 <<
"(" << concurrencyLevel <<
" worker process" << (concurrencyLevel > 1 ?
"es" :
"") <<
")\n";
384 executor = std::unique_ptr<Impl::TestRunnerExecutor>(
385 new Impl::WorkerProcessesTestRunnerExecutor(
386 writer, reportGenerator, useNamespaces, testCasesByGroup, testList, concurrencyLevel
394 writer <<
"Run type = process per test " 395 <<
"(" << concurrencyLevel <<
" simultaneous process" << (concurrencyLevel > 1 ?
"es" :
"") <<
")\n";
397 executor = std::unique_ptr<Impl::TestRunnerExecutor>(
398 new Impl::ProcessPerTestTestRunnerExecutor(
399 writer, reportGenerator, useNamespaces, testCasesByGroup, testList, concurrencyLevel
412 const bool success = report(*executor, duration);
415 writer << (
"\nTest application process with pid " +
::toString(getpid()) +
" finished execution.\n");
417 writer << (
"\nTest application parent process with pid " +
::toString(getpid()) +
" finished execution.");
421 writer <<
"Press a key to exit..\n";
422 struct termios oldattr = {};
423 tcgetattr(STDIN_FILENO, &oldattr);
424 struct termios newattr = oldattr;
425 newattr.c_lflag &= ~(ICANON | ECHO);
426 tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
428 tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
431 return success ? 0 : 1;
445 for (
size_t m = 0; m < count; m++) {
457 return getConcurrencyLevel(requestedConcurrency);
461 private:
unsigned int getConcurrencyLevel(
size_t requestedConcurrency = 0) {
462 const unsigned int coreCount = std::thread::hardware_concurrency();
463 const unsigned int maxConcurrency = Impl::TestRunnerLimits::MaxConcurrency;
465 if (requestedConcurrency != 0) {
466 if (requestedConcurrency > maxConcurrency) {
467 writer <<
"Requested concurrency level exceeds the maximum supported concurrency level of " 468 << maxConcurrency <<
". Setting concurrency level to " << maxConcurrency <<
"\n";
469 return maxConcurrency;
472 return requestedConcurrency;
473 }
else if (coreCount == 0) {
476 ,
"Could not determine the core count of the machine " 477 "(std::thread::hardware_concurrency() returned 0). " 478 "Please specify the concurrency level manually via " 479 "a test runner constructor argument." 481 }
else if (coreCount > maxConcurrency) {
482 writer <<
"Core count exceeds the maximum supported concurrency level of " 483 << maxConcurrency <<
". Setting concurrency level to " << maxConcurrency <<
"\n";
485 return maxConcurrency;
493 return desiredExecutionModel;
497 writer <<
"Warning: running WorkerThreads execution model (this platform does not support out of process test runs).\n";
503 private:
bool report(
const Impl::TestRunnerExecutor & runner, std::chrono::nanoseconds duration) {
504 const std::vector<Impl::TestResult> & testResults = runner.getTestResults();
505 const std::vector<Impl::FlattenedTestCase> & tests = runner.getTests();
507 const auto failureCount = (size_t) std::count_if(
510 , [] (
const Impl::TestResult & message) {
return message.result == Impl::TestResult::Result::Failure; }
513 const auto errorCount = (size_t) std::count_if(
519 const auto ignoredCount = (size_t) std::count_if(
522 , [] (
const Impl::TestResult & message) {
return message.result == Impl::TestResult::Result::Ignored; }
525 const size_t successCount = testResults.size() - ignoredCount - failureCount - errorCount;
526 const std::string totalCoreDuration = Impl::TestRunnerExecutor::durationStr(runner.getTotalCoreTime());
527 const std::string totalClockDuration = Impl::TestRunnerExecutor::durationStr(duration);
528 const std::string testAverageDuration = runner.getTestResults().empty() ?
"n/a" : Impl::TestRunnerExecutor::durationStr(runner.getTotalCoreTime() / runner.getTestResults().size());
530 writer <<
"\n------------------------- COMPLETED TESTS ------------------------\n" 531 <<
"\nTotal duration (test run clock time) = " << totalCoreDuration
532 <<
"\nAverage duration (test run clock time) = " << testAverageDuration
533 <<
"\nTotal duration (application clock time) = " << totalClockDuration <<
"\n";
535 if (failureCount == 0 && errorCount == 0) {
536 writer <<
"\nALL TESTS PASSED" 541 writer <<
"\n***** THERE WERE TEST FAILURES. *****\n" 542 <<
"\nTotal tests run: " << (successCount + ignoredCount + failureCount + errorCount) <<
"\n" 548 <<
"Failed/errored tests:\n";
553 , [&tests,
this] (
const Impl::TestResult & message) {
555 writer <<
" " << tests[message.testIndex].testName <<
"\n";
563 return failureCount == 0 && errorCount == 0;
566 private:
void registerTest(Impl::TestGroupBase * group,
567 const std::string & testGroupName,
568 const std::shared_ptr<Impl::TestMethodBase> & method,
569 const std::string & name) {
570 std::lock_guard<std::mutex> lock(mutex);
571 auto & testCases = testCasesByGroup[testGroupName];
572 testCases.emplace_back(Impl::TestCase(group, method, testGroupName +
"::" + name));
575 private:
void setUseNamespaces(
bool useNamespaces_) {
576 std::lock_guard<std::mutex> lock(mutex);
578 useNamespaces = useNamespaces_;
580 for (
auto & testCaseGroup : testCasesByGroup) {
581 auto & testCaseVector = testCaseGroup.second;
583 if (!testCaseVector.empty()) {
584 testCaseVector[0].group->setGroupName(
585 Impl::TestRunnerExecutor::extractTypeName(
586 testCaseVector[0].group->getGroupName().c_str(), useNamespaces
593 private:
void log(
const std::string &
string) {
597 private:
void logLine(
const std::string &
string) {
598 writer <<
string <<
"\n";
601 private:
void logLine() {
605 template <
typename T>
friend class TestGroup;
607 private: std::mutex mutex;
608 private: std::string testList;
609 private: std::unordered_map<std::string, std::vector<Impl::TestCase>> testCasesByGroup;
610 private: Impl::CompositeWriter writer;
611 private: std::shared_ptr<Impl::TestReportGenerator> reportGenerator;
612 private:
unsigned int concurrencyLevel {};
614 private:
bool useNamespaces;
615 private:
bool pauseAtExit;
616 private:
unsigned int currentGroupIndex = 0;
621 template <
typename TestClassT>
622 inline void TestGroup<TestClassT>::ignore() {
626 template <
typename TestClassT>
627 inline void TestGroup<TestClassT>::log(
const std::string &
string) {
628 return Testing::TestRunner::runner().log(
string);
631 template <
typename TestClassT>
632 template <
typename S,
typename ... SR>
633 inline void TestGroup<TestClassT>::log(
const S & p,
const SR & ... pRest) {
638 template <
typename TestClassT>
639 inline void TestGroup<TestClassT>::logLine(
const std::string &
string) {
640 return Testing::TestRunner::runner().logLine(
string);
643 template <
typename TestClassT>
644 template <
typename S,
typename ... SR>
645 inline void TestGroup<TestClassT>::logLine(
const S & p,
const SR & ... pRest) {
650 template <
typename TestClassT>
651 inline TestGroup<TestClassT>::TestGroup()
652 : TestGroupBase(TestRunner::runner())
654 , testGroupName(Impl::TestRunnerExecutor::extractTypeName(
typeid(*this).name(),
false)) {
658 template <
typename TestClassT>
659 inline TestGroup<TestClassT>::TestGroup(
unsigned int executionModels_)
660 : TestGroupBase(TestRunner::runner())
661 , executionModels(executionModels_)
662 , testGroupName(Impl::TestRunnerExecutor::extractTypeName(
typeid(*this).name(),
false)) {
666 template <
typename TestClassT>
667 inline void TestGroup<TestClassT>::registerTest(
Method method,
const std::string & testName) {
668 auto m = std::shared_ptr<Impl::TestMethodBase>(
new TestMethod(*static_cast<TestClassT *>(
this), method));
669 auto & runner = Testing::TestRunner::runner();
670 runner.registerTest(
this, testGroupName, m, testName);
675 #endif // COM_BORA_SOFTWARE__BALAU_TESTING__TEST_RUNNER static std::string padLeft(const std::string_view &str, unsigned int width, char c=' ')
Left pad the supplied UTF-8 string up to the specified width in code points.
Definition: Strings.hpp:603
Pre-defined test writer classes for stdout, stderr, and files.
A test writer that writes to stdout.
Definition: StdWriters.hpp:34
static int run(int argc, char *argv[], bool ignoreFirst, std::shared_ptr< Impl::TestReportGenerator > reportGenerator, const WriterItemT &... writerItems)
Run the test runner after parsing the supplied input arguments and using the supplied writers...
Definition: TestRunner.hpp:145
Test runner execution model enum.
The default implementation of the clock API.
Definition: SystemClock.hpp:30
static bool forkSupported()
Determine whether forking is support on this platform.
Definition: Fork.hpp:35
Run all tests in a single thread.
Definition: ExecutionModel.hpp:41
#define ThrowBalauException(ExceptionClass,...)
Throw a Balau style exception, with implicit file and line number, and optional stacktrace.
Definition: BalauException.hpp:45
bool hasOption(KeyT key) const
Does the command line option set have the specified option?
Definition: CommandLine.hpp:248
A write only standard file on a file system which is written as bytes.
A compact command line argument parser.
static int run(ExecutionModel executionModel, const Resource::File &reportOutputFolder, std::shared_ptr< Impl::TestReportGenerator > reportGenerator, const WriterItemT &... writerItems)
Run the test runner after parsing the supplied input arguments and using the supplied writers...
Definition: TestRunner.hpp:230
Thrown by the test runner when there is a non test related error.
Definition: TestExceptions.hpp:35
boost::beast::http::error Error
Boost error codes returned from HTTP algorithms and operations.
Definition: NetworkTypes.hpp:313
The main test runner class.
Definition: TestRunner.hpp:83
Run tests in a set of worker processes.
Definition: ExecutionModel.hpp:69
void parse(int argc, char *argv[], bool ignoreFirst)
Parse the input arguments.
Definition: CommandLine.hpp:174
Balau exceptions for the test framework.
boost::beast::http::verb Method
The HTTP method (GET, HEAD, POST).
Definition: NetworkTypes.hpp:298
std::string getFinalValue(size_t index=0) const
Get the final value with the specified index as a string or throw if no final value is available...
Definition: CommandLine.hpp:481
CommandLine & withOptionalFinalValue()
Specify that the command line arguments include one (SEV style) or one or more (SSV style) final stan...
Definition: CommandLine.hpp:156
Balau exceptions for system utilities.
Thrown when a bug is encountered (mainly unimplemented switch cases).
Definition: BalauException.hpp:178
Switches that have a leading dash '-' character.
static constexpr auto toUnderlying(E e) noexcept -> typename std::underlying_type< E >::type
Convert the strongly typed enum to its underlying integer.
Definition: Enums.hpp:31
std::string getHelpText(size_t indent, size_t totalWidth, CommandLineStyle styleOverride=CommandLineStyle::Detect) const
Get the help text.
Definition: CommandLine.hpp:569
Thrown when an illegal argument is passed to a function or method.
Definition: BalauException.hpp:138
boost::filesystem::directory_entry getEntry() const
Get the underlying directory entry for this file URI.
Definition: File.hpp:400
static std::string_view trim(const std::string_view &input)
Trim whitespace from the beginning and end of the supplied UTF-8 string.
Definition: Strings.hpp:706
Run all tests in a single process.
Definition: ExecutionModel.hpp:55
size_t getFinalValueCount() const
Get the number of final values available.
Definition: CommandLine.hpp:258
bool isExecutionModel(std::string_view value)
Returns true if the supplied string represents a valid execution model (the case is ignored)...
Definition: ExecutionModel.hpp:140
std::string getOption(KeyT key) const
Get the specified option value as a UTF-8 string.
Definition: CommandLine.hpp:269
CommandLine & withOption(KeyT key, const std::string &shortSwitch, const std::string &longSwitch, bool hasValue, const std::string &documentation)
Specify an option that has a short style switch and a long style switch.
Definition: CommandLine.hpp:100
A file on the local file system.
Definition: File.hpp:35
A compact command line argument parser.
Definition: CommandLine.hpp:60
A thread-local name (used by the logging system).
unsigned int getOptionAsUnsignedIntOrDefault(KeyT key, unsigned int defaultValue) const
Get the specified option value as an unsigned int, or the specified default if there is no such optio...
Definition: CommandLine.hpp:399
static std::string toLower(const std::string_view &s)
Convert the supplied UTF-8 string to lowercase.
Definition: Strings.hpp:457
virtual std::chrono::nanoseconds nanotime() const =0
Get the current time in nanoseconds since the unix epoch.
Balau exceptions for resources.
Assertion utilities for development purposes.
static int run(ExecutionModel executionModel, const Resource::File &reportOutputFolder=Resource::File(), std::shared_ptr< Impl::TestReportGenerator > reportGenerator=std::shared_ptr< Impl::TestReportGenerator >(new Reporters::SurefireTestReportGenerator))
Run all the tests in the test runner.
Definition: TestRunner.hpp:190
The test runner and test assertion functions.
Definition: Assertions.hpp:23
ExecutionModel
The type of execution model to be used by the test runner.
Definition: ExecutionModel.hpp:32
std::string toRawString() const override
Get a string representing the raw URI.
Definition: File.hpp:572
static void setName(const std::string &name)
Set the name of the calling thread.
static int run(int argc, char *argv[], bool ignoreFirst=true, std::shared_ptr< Impl::TestReportGenerator > reportGenerator=std::shared_ptr< Impl::TestReportGenerator >(new Reporters::SurefireTestReportGenerator))
Run the test runner after parsing the supplied input arguments.
Definition: TestRunner.hpp:97
void fromString(ExecutionModel &model, const std::string &value)
Obtain the execution model from a string.
Definition: ExecutionModel.hpp:123
Balau::U8String< AllocatorT > toString(ExecutionModel model)
Print the execution model value as a UTF-8 string.
Definition: ExecutionModel.hpp:92
Test group report generator that generates XML reports with the Maven Surefire plugin schema...
Definition: SurefireTestReportGenerator.hpp:21
Run each test in a separate worker process.
Definition: ExecutionModel.hpp:83