// Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once #include #include #include #include namespace beast::unit_test { /** * Unit test runner interface. * * Derived classes can customize the reporting behavior. This interface is * injected into the unit_test class to receive the results of the tests. */ class Runner { std::string arg_; bool default_ = false; bool failed_ = false; bool cond_ = false; std::recursive_mutex mutex_; public: Runner() = default; virtual ~Runner() = default; Runner(Runner const&) = delete; Runner& operator=(Runner const&) = delete; /** * Set the argument string. * * The argument string is available to suites and * allows for customization of the test. Each suite * defines its own syntax for the argument string. * The same argument is passed to all suites. */ void arg(std::string const& s) { arg_ = s; } /** * Returns the argument string. */ [[nodiscard]] std::string const& arg() const { return arg_; } /** * Run the specified suite. * @return `true` if any conditions failed. */ template bool run(SuiteInfo const& s); /** * Run a sequence of suites. * The expression * `FwdIter::value_type` * must be convertible to `SuiteInfo`. * @return `true` if any conditions failed. */ template bool run(FwdIter first, FwdIter last); /** * Conditionally run a sequence of suites. * pred will be called as: * @code * bool pred(SuiteInfo const&); * @endcode * @return `true` if any conditions failed. */ template bool runIf(FwdIter first, FwdIter last, Pred pred = Pred{}); /** * Run all suites in a container. * @return `true` if any conditions failed. */ template bool runEach(SequenceContainer const& c); /** * Conditionally run suites in a container. * pred will be called as: * @code * bool pred(SuiteInfo const&); * @endcode * @return `true` if any conditions failed. */ template bool runEachIf(SequenceContainer const& c, Pred pred = Pred{}); protected: /** * Called when a new suite starts. */ virtual void onSuiteBegin(SuiteInfo const&) { } /** * Called when a suite ends. */ virtual void onSuiteEnd() { } /** * Called when a new case starts. */ virtual void onCaseBegin(std::string const&) { } /** * Called when a new case ends. */ virtual void onCaseEnd() { } /** * Called for each passing condition. */ virtual void onPass() { } /** * Called for each failing condition. */ virtual void onFail(std::string const&) { } /** * Called when a test logs output. */ virtual void onLog(std::string const&) { } private: friend class Suite; // Start a new testcase. template void testcase(std::string const& name); template void pass(); template void fail(std::string const& reason); template void log(std::string const& s); }; //------------------------------------------------------------------------------ template bool Runner::run(SuiteInfo const& s) { // Enable 'default' testcase default_ = true; failed_ = false; onSuiteBegin(s); s.run(*this); // Forgot to call pass or fail. BOOST_ASSERT(cond_); onCaseEnd(); onSuiteEnd(); return failed_; } template bool Runner::run(FwdIter first, FwdIter last) { bool failed(false); for (; first != last; ++first) failed = run(*first) || failed; return failed; } template bool Runner::runIf(FwdIter first, FwdIter last, Pred pred) { bool failed(false); for (; first != last; ++first) { if (pred(*first)) failed = run(*first) || failed; } return failed; } template bool Runner::runEach(SequenceContainer const& c) { bool failed(false); for (auto const& s : c) failed = run(s) || failed; return failed; } template bool Runner::runEachIf(SequenceContainer const& c, Pred pred) { bool failed(false); for (auto const& s : c) { if (pred(s)) failed = run(s) || failed; } return failed; } template void Runner::testcase(std::string const& name) { std::scoped_lock const lock(mutex_); // Name may not be empty BOOST_ASSERT(default_ || !name.empty()); // Forgot to call pass or fail BOOST_ASSERT(default_ || cond_); if (!default_) onCaseEnd(); default_ = false; cond_ = false; onCaseBegin(name); } template void Runner::pass() { std::scoped_lock const lock(mutex_); if (default_) testcase(""); onPass(); cond_ = true; } template void Runner::fail(std::string const& reason) { std::scoped_lock const lock(mutex_); if (default_) testcase(""); onFail(reason); failed_ = true; cond_ = true; } template void Runner::log(std::string const& s) { std::scoped_lock const lock(mutex_); if (default_) testcase(""); onLog(s); } } // namespace beast::unit_test