From 9f5ea1daf9ad262fb909a45fca774b66707eea8b Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 15 Oct 2017 07:37:58 -0700 Subject: [PATCH] Squashed 'subtree/unit_test/' content from commit e8af9ee63 git-subtree-dir: subtree/unit_test git-subtree-split: e8af9ee6379d6728618927f8820c8042bd3f8866 --- README.md | 3 + include/boost/beast/unit_test/amount.hpp | 59 ++ .../unit_test/detail/const_container.hpp | 95 +++ include/boost/beast/unit_test/dstream.hpp | 130 ++++ .../boost/beast/unit_test/global_suites.hpp | 55 ++ include/boost/beast/unit_test/main.cpp | 87 +++ include/boost/beast/unit_test/match.hpp | 177 +++++ include/boost/beast/unit_test/recorder.hpp | 96 +++ include/boost/beast/unit_test/reporter.hpp | 292 ++++++++ include/boost/beast/unit_test/results.hpp | 246 ++++++ include/boost/beast/unit_test/runner.hpp | 292 ++++++++ include/boost/beast/unit_test/suite.hpp | 699 ++++++++++++++++++ include/boost/beast/unit_test/suite_info.hpp | 126 ++++ include/boost/beast/unit_test/suite_list.hpp | 81 ++ include/boost/beast/unit_test/thread.hpp | 128 ++++ 15 files changed, 2566 insertions(+) create mode 100644 README.md create mode 100644 include/boost/beast/unit_test/amount.hpp create mode 100644 include/boost/beast/unit_test/detail/const_container.hpp create mode 100644 include/boost/beast/unit_test/dstream.hpp create mode 100644 include/boost/beast/unit_test/global_suites.hpp create mode 100644 include/boost/beast/unit_test/main.cpp create mode 100644 include/boost/beast/unit_test/match.hpp create mode 100644 include/boost/beast/unit_test/recorder.hpp create mode 100644 include/boost/beast/unit_test/reporter.hpp create mode 100644 include/boost/beast/unit_test/results.hpp create mode 100644 include/boost/beast/unit_test/runner.hpp create mode 100644 include/boost/beast/unit_test/suite.hpp create mode 100644 include/boost/beast/unit_test/suite_info.hpp create mode 100644 include/boost/beast/unit_test/suite_list.hpp create mode 100644 include/boost/beast/unit_test/thread.hpp diff --git a/README.md b/README.md new file mode 100644 index 00000000..d3afed09 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# unit_test + +A simple class-based unit test framework copied from Boost.Beast diff --git a/include/boost/beast/unit_test/amount.hpp b/include/boost/beast/unit_test/amount.hpp new file mode 100644 index 00000000..13e7c9f9 --- /dev/null +++ b/include/boost/beast/unit_test/amount.hpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_AMOUNT_HPP +#define BOOST_BEAST_UNIT_TEST_AMOUNT_HPP + +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +/** Utility for producing nicely composed output of amounts with units. */ +class amount +{ +private: + std::size_t n_; + std::string const& what_; + +public: + amount(amount const&) = default; + amount& operator=(amount const&) = delete; + + template + amount(std::size_t n, std::string const& what); + + friend + std::ostream& + operator<<(std::ostream& s, amount const& t); +}; + +template +amount::amount(std::size_t n, std::string const& what) + : n_(n) + , what_(what) +{ +} + +inline +std::ostream& +operator<<(std::ostream& s, amount const& t) +{ + s << t.n_ << " " << t.what_ <<((t.n_ != 1) ? "s" : ""); + return s; +} + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/detail/const_container.hpp b/include/boost/beast/unit_test/detail/const_container.hpp new file mode 100644 index 00000000..7a6be1b3 --- /dev/null +++ b/include/boost/beast/unit_test/detail/const_container.hpp @@ -0,0 +1,95 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_DETAIL_CONST_CONTAINER_HPP +#define BOOST_BEAST_UNIT_TEST_DETAIL_CONST_CONTAINER_HPP + +namespace boost { +namespace beast { +namespace unit_test { +namespace detail { + +/** Adapter to constrain a container interface. + The interface allows for limited read only operations. Derived classes + provide additional behavior. +*/ +template +class const_container +{ +private: + using cont_type = Container; + + cont_type m_cont; + +protected: + cont_type& cont() + { + return m_cont; + } + + cont_type const& cont() const + { + return m_cont; + } + +public: + using value_type = typename cont_type::value_type; + using size_type = typename cont_type::size_type; + using difference_type = typename cont_type::difference_type; + using iterator = typename cont_type::const_iterator; + using const_iterator = typename cont_type::const_iterator; + + /** Returns `true` if the container is empty. */ + bool + empty() const + { + return m_cont.empty(); + } + + /** Returns the number of items in the container. */ + size_type + size() const + { + return m_cont.size(); + } + + /** Returns forward iterators for traversal. */ + /** @{ */ + const_iterator + begin() const + { + return m_cont.cbegin(); + } + + const_iterator + cbegin() const + { + return m_cont.cbegin(); + } + + const_iterator + end() const + { + return m_cont.cend(); + } + + const_iterator + cend() const + { + return m_cont.cend(); + } + /** @} */ +}; + +} // detail +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/dstream.hpp b/include/boost/beast/unit_test/dstream.hpp new file mode 100644 index 00000000..599b64b5 --- /dev/null +++ b/include/boost/beast/unit_test/dstream.hpp @@ -0,0 +1,130 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_DSTREAM_HPP +#define BOOST_BEAST_UNIT_TEST_DSTREAM_HPP + +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_WINDOWS +#include +#include +#endif + +namespace boost { +namespace beast { +namespace unit_test { + +#ifdef BOOST_WINDOWS + +namespace detail { + +template +class dstream_buf + : public std::basic_stringbuf +{ + using ostream = std::basic_ostream; + + ostream& os_; + bool dbg_; + + template + void write(T const*) = delete; + + void write(char const* s) + { + if(dbg_) + boost::detail::winapi::OutputDebugStringA(s); + os_ << s; + } + + void write(wchar_t const* s) + { + if(dbg_) + boost::detail::winapi::OutputDebugStringW(s); + os_ << s; + } + +public: + explicit + dstream_buf(ostream& os) + : os_(os) + , dbg_(boost::detail::winapi::IsDebuggerPresent() != 0) + { + } + + ~dstream_buf() + { + sync(); + } + + int + sync() override + { + write(this->str().c_str()); + this->str(""); + return 0; + } +}; + +} // detail + +/** std::ostream with Visual Studio IDE redirection. + + Instances of this stream wrap a specified `std::ostream` + (such as `std::cout` or `std::cerr`). If the IDE debugger + is attached when the stream is created, output will be + additionally copied to the Visual Studio Output window. +*/ +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator +> +class basic_dstream + : public std::basic_ostream +{ + detail::dstream_buf< + CharT, Traits, Allocator> buf_; + +public: + /** Construct a stream. + + @param os The output stream to wrap. + */ + explicit + basic_dstream(std::ostream& os) + : std::basic_ostream(&buf_) + , buf_(os) + { + if(os.flags() & std::ios::unitbuf) + std::unitbuf(*this); + } +}; + +using dstream = basic_dstream; +using dwstream = basic_dstream; + +#else + +using dstream = std::ostream&; +using dwstream = std::wostream&; + +#endif + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/global_suites.hpp b/include/boost/beast/unit_test/global_suites.hpp new file mode 100644 index 00000000..56e49a44 --- /dev/null +++ b/include/boost/beast/unit_test/global_suites.hpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_GLOBAL_SUITES_HPP +#define BOOST_BEAST_UNIT_TEST_GLOBAL_SUITES_HPP + +#include + +namespace boost { +namespace beast { +namespace unit_test { + +namespace detail { + +/// Holds test suites registered during static initialization. +inline +suite_list& +global_suites() +{ + static suite_list s; + return s; +} + +template +struct insert_suite +{ + insert_suite(char const* name, char const* module, + char const* library, bool manual) + { + global_suites().insert( + name, module, library, manual); + } +}; + +} // detail + +/// Holds test suites registered during static initialization. +inline +suite_list const& +global_suites() +{ + return detail::global_suites(); +} + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/main.cpp b/include/boost/beast/unit_test/main.cpp new file mode 100644 index 00000000..04b6aed9 --- /dev/null +++ b/include/boost/beast/unit_test/main.cpp @@ -0,0 +1,87 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_MSVC +# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +# include +# undef WIN32_LEAN_AND_MEAN +# else +# include +# endif +#endif + +// Simple main used to produce stand +// alone executables that run unit tests. +int main(int ac, char const* av[]) +{ + using namespace std; + using namespace boost::beast::unit_test; + + dstream log(std::cerr); + std::unitbuf(log); + +#if BOOST_MSVC + { + int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flags |= _CRTDBG_LEAK_CHECK_DF; + _CrtSetDbgFlag(flags); + } +#endif + + if(ac == 2) + { + std::string const s{av[1]}; + if(s == "-h" || s == "--help") + { + log << + "Usage:\n" + " " << av[0] << ": { ... }" << + std::endl; + return EXIT_SUCCESS; + } + } + + reporter r(log); + bool failed; + if(ac > 1) + { + std::vector v; + v.reserve(ac - 1); + for(int i = 1; i < ac; ++i) + v.emplace_back(selector::automatch, av[i]); + auto pred = + [&v](suite_info const& si) mutable + { + for(auto& p : v) + if(p(si)) + return true; + return false; + }; + failed = r.run_each_if(global_suites(), pred); + } + else + { + failed = r.run_each(global_suites()); + } + if(failed) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} diff --git a/include/boost/beast/unit_test/match.hpp b/include/boost/beast/unit_test/match.hpp new file mode 100644 index 00000000..39c3c787 --- /dev/null +++ b/include/boost/beast/unit_test/match.hpp @@ -0,0 +1,177 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_MATCH_HPP +#define BOOST_BEAST_UNIT_TEST_MATCH_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +// Predicate for implementing matches +class selector +{ +public: + enum mode_t + { + // Run all tests except manual ones + all, + + // Run tests that match in any field + automatch, + + // Match on suite + suite, + + // Match on library + library, + + // Match on module (used internally) + module, + + // Match nothing (used internally) + none + }; + +private: + mode_t mode_; + std::string pat_; + std::string library_; + +public: + template + explicit + selector(mode_t mode, std::string const& pattern = ""); + + template + bool + operator()(suite_info const& s); +}; + +//------------------------------------------------------------------------------ + +template +selector::selector(mode_t mode, std::string const& pattern) + : mode_(mode) + , pat_(pattern) +{ + if(mode_ == automatch && pattern.empty()) + mode_ = all; +} + +template +bool +selector::operator()(suite_info const& s) +{ + switch(mode_) + { + case automatch: + // suite or full name + if(s.name() == pat_ || s.full_name() == pat_) + { + mode_ = none; + return true; + } + + // check module + if(pat_ == s.module()) + { + mode_ = module; + library_ = s.library(); + return ! s.manual(); + } + + // check library + if(pat_ == s.library()) + { + mode_ = library; + return ! s.manual(); + } + + return false; + + case suite: + return pat_ == s.name(); + + case module: + return pat_ == s.module() && ! s.manual(); + + case library: + return pat_ == s.library() && ! s.manual(); + + case none: + return false; + + case all: + default: + // fall through + break; + }; + + return ! s.manual(); +} + +//------------------------------------------------------------------------------ + +// Utility functions for producing predicates to select suites. + +/** Returns a predicate that implements a smart matching rule. + The predicate checks the suite, module, and library fields of the + suite_info in that order. When it finds a match, it changes modes + depending on what was found: + + If a suite is matched first, then only the suite is selected. The + suite may be marked manual. + + If a module is matched first, then only suites from that module + and library not marked manual are selected from then on. + + If a library is matched first, then only suites from that library + not marked manual are selected from then on. + +*/ +inline +selector +match_auto(std::string const& name) +{ + return selector(selector::automatch, name); +} + +/** Return a predicate that matches all suites not marked manual. */ +inline +selector +match_all() +{ + return selector(selector::all); +} + +/** Returns a predicate that matches a specific suite. */ +inline +selector +match_suite(std::string const& name) +{ + return selector(selector::suite, name); +} + +/** Returns a predicate that matches all suites in a library. */ +inline +selector +match_library(std::string const& name) +{ + return selector(selector::library, name); +} + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/recorder.hpp b/include/boost/beast/unit_test/recorder.hpp new file mode 100644 index 00000000..a3fbfd6c --- /dev/null +++ b/include/boost/beast/unit_test/recorder.hpp @@ -0,0 +1,96 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_RECORDER_HPP +#define BOOST_BEAST_UNIT_TEST_RECORDER_HPP + +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +/** A test runner that stores the results. */ +class recorder : public runner +{ +private: + results m_results; + suite_results m_suite; + case_results m_case; + +public: + recorder() = default; + recorder(recorder const&) = default; + recorder& operator=(recorder const&) = default; + + /** Returns a report with the results of all completed suites. */ + results const& + report() const + { + return m_results; + } + +private: + virtual + void + on_suite_begin(suite_info const& info) override + { + m_suite = suite_results(info.full_name()); + } + + virtual + void + on_suite_end() override + { + m_results.insert(std::move(m_suite)); + } + + virtual + void + on_case_begin(std::string const& name) override + { + m_case = case_results(name); + } + + virtual + void + on_case_end() override + { + if(m_case.tests.size() > 0) + m_suite.insert(std::move(m_case)); + } + + virtual + void + on_pass() override + { + m_case.tests.pass(); + } + + virtual + void + on_fail(std::string const& reason) override + { + m_case.tests.fail(reason); + } + + virtual + void + on_log(std::string const& s) override + { + m_case.log.insert(s); + } +}; + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/reporter.hpp b/include/boost/beast/unit_test/reporter.hpp new file mode 100644 index 00000000..489744e1 --- /dev/null +++ b/include/boost/beast/unit_test/reporter.hpp @@ -0,0 +1,292 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_REPORTER_HPP +#define BOOST_BEAST_UNIT_TEST_REPORTER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +namespace detail { + +/** A simple test runner that writes everything to a stream in real time. + The totals are output when the object is destroyed. +*/ +template +class reporter : public runner +{ +private: + using clock_type = std::chrono::steady_clock; + + struct case_results + { + std::string name; + std::size_t total = 0; + std::size_t failed = 0; + + explicit + case_results(std::string name_ = "") + : name(std::move(name_)) + { + } + }; + + struct suite_results + { + std::string name; + std::size_t cases = 0; + std::size_t total = 0; + std::size_t failed = 0; + typename clock_type::time_point start = clock_type::now(); + + explicit + suite_results(std::string name_ = "") + : name(std::move(name_)) + { + } + + void + add(case_results const& r); + }; + + struct results + { + using run_time = std::pair; + + enum + { + max_top = 10 + }; + + std::size_t suites = 0; + std::size_t cases = 0; + std::size_t total = 0; + std::size_t failed = 0; + std::vector top; + typename clock_type::time_point start = clock_type::now(); + + void + add(suite_results const& r); + }; + + std::ostream& os_; + results results_; + suite_results suite_results_; + case_results case_results_; + +public: + reporter(reporter const&) = delete; + reporter& operator=(reporter const&) = delete; + + ~reporter(); + + explicit + reporter(std::ostream& os = std::cout); + +private: + static + std::string + fmtdur(typename clock_type::duration const& d); + + virtual + void + on_suite_begin(suite_info const& info) override; + + virtual + void + on_suite_end() override; + + virtual + void + on_case_begin(std::string const& name) override; + + virtual + void + on_case_end() override; + + virtual + void + on_pass() override; + + virtual + void + on_fail(std::string const& reason) override; + + virtual + void + on_log(std::string const& s) override; +}; + +//------------------------------------------------------------------------------ + +template +void +reporter<_>:: +suite_results::add(case_results const& r) +{ + ++cases; + total += r.total; + failed += r.failed; +} + +template +void +reporter<_>:: +results::add(suite_results const& r) +{ + ++suites; + total += r.total; + cases += r.cases; + failed += r.failed; + auto const elapsed = clock_type::now() - r.start; + if(elapsed >= std::chrono::seconds{1}) + { + auto const iter = std::lower_bound(top.begin(), + top.end(), elapsed, + [](run_time const& t1, + typename clock_type::duration const& t2) + { + return t1.second > t2; + }); + if(iter != top.end()) + { + top.emplace(iter, r.name, elapsed); + if(top.size() > max_top) + top.resize(max_top); + } + } +} + +//------------------------------------------------------------------------------ + +template +reporter<_>:: +reporter(std::ostream& os) + : os_(os) +{ +} + +template +reporter<_>::~reporter() +{ + if(results_.top.size() > 0) + { + os_ << "Longest suite times:\n"; + for(auto const& i : results_.top) + os_ << std::setw(8) << + fmtdur(i.second) << " " << i.first << '\n'; + } + auto const elapsed = clock_type::now() - results_.start; + os_ << + fmtdur(elapsed) << ", " << + amount{results_.suites, "suite"} << ", " << + amount{results_.cases, "case"} << ", " << + amount{results_.total, "test"} << " total, " << + amount{results_.failed, "failure"} << + std::endl; +} + +template +std::string +reporter<_>::fmtdur(typename clock_type::duration const& d) +{ + using namespace std::chrono; + auto const ms = duration_cast(d); + if(ms < seconds{1}) + return std::to_string(ms.count()) + "ms"; + std::stringstream ss; + ss << std::fixed << std::setprecision(1) << + (ms.count()/1000.) << "s"; + return ss.str(); +} + +template +void +reporter<_>:: +on_suite_begin(suite_info const& info) +{ + suite_results_ = suite_results{info.full_name()}; +} + +template +void +reporter<_>::on_suite_end() +{ + results_.add(suite_results_); +} + +template +void +reporter<_>:: +on_case_begin(std::string const& name) +{ + case_results_ = case_results(name); + os_ << suite_results_.name << + (case_results_.name.empty() ? "" : + (" " + case_results_.name)) << std::endl; +} + +template +void +reporter<_>:: +on_case_end() +{ + suite_results_.add(case_results_); +} + +template +void +reporter<_>:: +on_pass() +{ + ++case_results_.total; +} + +template +void +reporter<_>:: +on_fail(std::string const& reason) +{ + ++case_results_.failed; + ++case_results_.total; + os_ << + "#" << case_results_.total << " failed" << + (reason.empty() ? "" : ": ") << reason << std::endl; +} + +template +void +reporter<_>:: +on_log(std::string const& s) +{ + os_ << s; +} + +} // detail + +using reporter = detail::reporter<>; + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/results.hpp b/include/boost/beast/unit_test/results.hpp new file mode 100644 index 00000000..5fe3b7ba --- /dev/null +++ b/include/boost/beast/unit_test/results.hpp @@ -0,0 +1,246 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_RESULTS_HPP +#define BOOST_BEAST_UNIT_TEST_RESULTS_HPP + +#include + +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +/** Holds a set of test condition outcomes in a testcase. */ +class case_results +{ +public: + /** Holds the result of evaluating one test condition. */ + struct test + { + explicit test(bool pass_) + : pass(pass_) + { + } + + test(bool pass_, std::string const& reason_) + : pass(pass_) + , reason(reason_) + { + } + + bool pass; + std::string reason; + }; + +private: + class tests_t + : public detail::const_container > + { + private: + std::size_t failed_; + + public: + tests_t() + : failed_(0) + { + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return cont().size(); + } + + /** Returns the number of failed test conditions. */ + std::size_t + failed() const + { + return failed_; + } + + /** Register a successful test condition. */ + void + pass() + { + cont().emplace_back(true); + } + + /** Register a failed test condition. */ + void + fail(std::string const& reason = "") + { + ++failed_; + cont().emplace_back(false, reason); + } + }; + + class log_t + : public detail::const_container > + { + public: + /** Insert a string into the log. */ + void + insert(std::string const& s) + { + cont().push_back(s); + } + }; + + std::string name_; + +public: + explicit case_results(std::string const& name = "") + : name_(name) + { + } + + /** Returns the name of this testcase. */ + std::string const& + name() const + { + return name_; + } + + /** Memberspace for a container of test condition outcomes. */ + tests_t tests; + + /** Memberspace for a container of testcase log messages. */ + log_t log; +}; + +//-------------------------------------------------------------------------- + +/** Holds the set of testcase results in a suite. */ +class suite_results + : public detail::const_container > +{ +private: + std::string name_; + std::size_t total_ = 0; + std::size_t failed_ = 0; + +public: + explicit suite_results(std::string const& name = "") + : name_(name) + { + } + + /** Returns the name of this suite. */ + std::string const& + name() const + { + return name_; + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return total_; + } + + /** Returns the number of failures. */ + std::size_t + failed() const + { + return failed_; + } + + /** Insert a set of testcase results. */ + /** @{ */ + void + insert(case_results&& r) + { + cont().emplace_back(std::move(r)); + total_ += r.tests.total(); + failed_ += r.tests.failed(); + } + + void + insert(case_results const& r) + { + cont().push_back(r); + total_ += r.tests.total(); + failed_ += r.tests.failed(); + } + /** @} */ +}; + +//------------------------------------------------------------------------------ + +// VFALCO TODO Make this a template class using scoped allocators +/** Holds the results of running a set of testsuites. */ +class results + : public detail::const_container > +{ +private: + std::size_t m_cases; + std::size_t total_; + std::size_t failed_; + +public: + results() + : m_cases(0) + , total_(0) + , failed_(0) + { + } + + /** Returns the total number of test cases. */ + std::size_t + cases() const + { + return m_cases; + } + + /** Returns the total number of test conditions. */ + std::size_t + total() const + { + return total_; + } + + /** Returns the number of failures. */ + std::size_t + failed() const + { + return failed_; + } + + /** Insert a set of suite results. */ + /** @{ */ + void + insert(suite_results&& r) + { + m_cases += r.size(); + total_ += r.total(); + failed_ += r.failed(); + cont().emplace_back(std::move(r)); + } + + void + insert(suite_results const& r) + { + m_cases += r.size(); + total_ += r.total(); + failed_ += r.failed(); + cont().push_back(r); + } + /** @} */ +}; + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/runner.hpp b/include/boost/beast/unit_test/runner.hpp new file mode 100644 index 00000000..da849376 --- /dev/null +++ b/include/boost/beast/unit_test/runner.hpp @@ -0,0 +1,292 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_RUNNER_H_INCLUDED +#define BOOST_BEAST_UNIT_TEST_RUNNER_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace 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 argumnet string. + The same argument is passed to all suites. + */ + void + arg(std::string const& s) + { + arg_ = s; + } + + /** Returns the argument string. */ + std::string const& + arg() const + { + return arg_; + } + + /** Run the specified suite. + @return `true` if any conditions failed. + */ + template + bool + run(suite_info const& s); + + /** Run a sequence of suites. + The expression + `FwdIter::value_type` + must be convertible to `suite_info`. + @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(suite_info const&); + @endcode + @return `true` if any conditions failed. + */ + template + bool + run_if(FwdIter first, FwdIter last, Pred pred = Pred{}); + + /** Run all suites in a container. + @return `true` if any conditions failed. + */ + template + bool + run_each(SequenceContainer const& c); + + /** Conditionally run suites in a container. + pred will be called as: + @code + bool pred(suite_info const&); + @endcode + @return `true` if any conditions failed. + */ + template + bool + run_each_if(SequenceContainer const& c, Pred pred = Pred{}); + +protected: + /// Called when a new suite starts. + virtual + void + on_suite_begin(suite_info const&) + { + } + + /// Called when a suite ends. + virtual + void + on_suite_end() + { + } + + /// Called when a new case starts. + virtual + void + on_case_begin(std::string const&) + { + } + + /// Called when a new case ends. + virtual + void + on_case_end() + { + } + + /// Called for each passing condition. + virtual + void + on_pass() + { + } + + /// Called for each failing condition. + virtual + void + on_fail(std::string const&) + { + } + + /// Called when a test logs output. + virtual + void + on_log(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(suite_info const& s) +{ + // Enable 'default' testcase + default_ = true; + failed_ = false; + on_suite_begin(s); + s.run(*this); + // Forgot to call pass or fail. + BOOST_ASSERT(cond_); + on_case_end(); + on_suite_end(); + 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::run_if(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::run_each(SequenceContainer const& c) +{ + bool failed(false); + for(auto const& s : c) + failed = run(s) || failed; + return failed; +} + +template +bool +runner::run_each_if(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::lock_guard lock(mutex_); + // Name may not be empty + BOOST_ASSERT(default_ || ! name.empty()); + // Forgot to call pass or fail + BOOST_ASSERT(default_ || cond_); + if(! default_) + on_case_end(); + default_ = false; + cond_ = false; + on_case_begin(name); +} + +template +void +runner::pass() +{ + std::lock_guard lock(mutex_); + if(default_) + testcase(""); + on_pass(); + cond_ = true; +} + +template +void +runner::fail(std::string const& reason) +{ + std::lock_guard lock(mutex_); + if(default_) + testcase(""); + on_fail(reason); + failed_ = true; + cond_ = true; +} + +template +void +runner::log(std::string const& s) +{ + std::lock_guard lock(mutex_); + if(default_) + testcase(""); + on_log(s); +} + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/suite.hpp b/include/boost/beast/unit_test/suite.hpp new file mode 100644 index 00000000..b8350a0f --- /dev/null +++ b/include/boost/beast/unit_test/suite.hpp @@ -0,0 +1,699 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_SUITE_HPP +#define BOOST_BEAST_UNIT_TEST_SUITE_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +namespace detail { + +template +static +std::string +make_reason(String const& reason, + char const* file, int line) +{ + std::string s(reason); + if(! s.empty()) + s.append(": "); + char const* path = file + strlen(file); + while(path != file) + { + #ifdef _MSC_VER + if(path[-1] == '\\') + #else + if(path[-1] == '/') + #endif + break; + --path; + } + s.append(path); + s.append("("); + s.append(std::to_string(line)); + s.append(")"); + return s; +} + +} // detail + +class thread; + +enum abort_t +{ + no_abort_on_fail, + abort_on_fail +}; + +/** A testsuite class. + + Derived classes execute a series of testcases, where each testcase is + a series of pass/fail tests. To provide a unit test using this class, + derive from it and use the BOOST_BEAST_DEFINE_UNIT_TEST macro in a + translation unit. +*/ +class suite +{ +private: + bool abort_ = false; + bool aborted_ = false; + runner* runner_ = nullptr; + + // This exception is thrown internally to stop the current suite + // in the event of a failure, if the option to stop is set. + struct abort_exception : public std::exception + { + char const* + what() const noexcept override + { + return "test suite aborted"; + } + }; + + template + class log_buf + : public std::basic_stringbuf + { + suite& suite_; + + public: + explicit + log_buf(suite& self) + : suite_(self) + { + } + + ~log_buf() + { + sync(); + } + + int + sync() override + { + auto const& s = this->str(); + if(s.size() > 0) + suite_.runner_->log(s); + this->str(""); + return 0; + } + }; + + template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator + > + class log_os : public std::basic_ostream + { + log_buf buf_; + + public: + explicit + log_os(suite& self) + : std::basic_ostream(&buf_) + , buf_(self) + { + } + }; + + class scoped_testcase; + + class testcase_t + { + suite& suite_; + std::stringstream ss_; + + public: + explicit + testcase_t(suite& self) + : suite_(self) + { + } + + /** Open a new testcase. + + A testcase is a series of evaluated test conditions. A test + suite may have multiple test cases. A test is associated with + the last opened testcase. When the test first runs, a default + unnamed case is opened. Tests with only one case may omit the + call to testcase. + + @param abort Determines if suite continues running after a failure. + */ + void + operator()(std::string const& name, + abort_t abort = no_abort_on_fail); + + scoped_testcase + operator()(abort_t abort); + + template + scoped_testcase + operator<<(T const& t); + }; + +public: + /** Logging output stream. + + Text sent to the log output stream will be forwarded to + the output stream associated with the runner. + */ + log_os log; + + /** Memberspace for declaring test cases. */ + testcase_t testcase; + + /** Returns the "current" running suite. + If no suite is running, nullptr is returned. + */ + static + suite* + this_suite() + { + return *p_this_suite(); + } + + suite() + : log(*this) + , testcase(*this) + { + } + + /** Invokes the test using the specified runner. + + Data members are set up here instead of the constructor as a + convenience to writing the derived class to avoid repetition of + forwarded constructor arguments to the base. + Normally this is called by the framework for you. + */ + template + void + operator()(runner& r); + + /** Record a successful test condition. */ + template + void + pass(); + + /** Record a failure. + + @param reason Optional text added to the output on a failure. + + @param file The source code file where the test failed. + + @param line The source code line number where the test failed. + */ + /** @{ */ + template + void + fail(String const& reason, char const* file, int line); + + template + void + fail(std::string const& reason = ""); + /** @} */ + + /** Evaluate a test condition. + + This function provides improved logging by incorporating the + file name and line number into the reported output on failure, + as well as additional text specified by the caller. + + @param shouldBeTrue The condition to test. The condition + is evaluated in a boolean context. + + @param reason Optional added text to output on a failure. + + @param file The source code file where the test failed. + + @param line The source code line number where the test failed. + + @return `true` if the test condition indicates success. + */ + /** @{ */ + template + bool + expect(Condition const& shouldBeTrue) + { + return expect(shouldBeTrue, ""); + } + + template + bool + expect(Condition const& shouldBeTrue, String const& reason); + + template + bool + expect(Condition const& shouldBeTrue, + char const* file, int line) + { + return expect(shouldBeTrue, "", file, line); + } + + template + bool + expect(Condition const& shouldBeTrue, + String const& reason, char const* file, int line); + /** @} */ + + // + // DEPRECATED + // + // Expect an exception from f() + template + bool + except(F&& f, String const& reason); + template + bool + except(F&& f) + { + return except(f, ""); + } + template + bool + except(F&& f, String const& reason); + template + bool + except(F&& f) + { + return except(f, ""); + } + template + bool + unexcept(F&& f, String const& reason); + template + bool + unexcept(F&& f) + { + return unexcept(f, ""); + } + + /** Return the argument associated with the runner. */ + std::string const& + arg() const + { + return runner_->arg(); + } + + // DEPRECATED + // @return `true` if the test condition indicates success(a false value) + template + bool + unexpected(Condition shouldBeFalse, + String const& reason); + + template + bool + unexpected(Condition shouldBeFalse) + { + return unexpected(shouldBeFalse, ""); + } + +private: + friend class thread; + + static + suite** + p_this_suite() + { + static suite* pts = nullptr; + return &pts; + } + + /** Runs the suite. */ + virtual + void + run() = 0; + + void + propagate_abort(); + + template + void + run(runner& r); +}; + +//------------------------------------------------------------------------------ + +// Helper for streaming testcase names +class suite::scoped_testcase +{ +private: + suite& suite_; + std::stringstream& ss_; + +public: + scoped_testcase& operator=(scoped_testcase const&) = delete; + + ~scoped_testcase() + { + auto const& name = ss_.str(); + if(! name.empty()) + suite_.runner_->testcase(name); + } + + scoped_testcase(suite& self, std::stringstream& ss) + : suite_(self) + , ss_(ss) + { + ss_.clear(); + ss_.str({}); + } + + template + scoped_testcase(suite& self, + std::stringstream& ss, T const& t) + : suite_(self) + , ss_(ss) + { + ss_.clear(); + ss_.str({}); + ss_ << t; + } + + template + scoped_testcase& + operator<<(T const& t) + { + ss_ << t; + return *this; + } +}; + +//------------------------------------------------------------------------------ + +inline +void +suite::testcase_t::operator()( + std::string const& name, abort_t abort) +{ + suite_.abort_ = abort == abort_on_fail; + suite_.runner_->testcase(name); +} + +inline +suite::scoped_testcase +suite::testcase_t::operator()(abort_t abort) +{ + suite_.abort_ = abort == abort_on_fail; + return { suite_, ss_ }; +} + +template +inline +suite::scoped_testcase +suite::testcase_t::operator<<(T const& t) +{ + return { suite_, ss_, t }; +} + +//------------------------------------------------------------------------------ + +template +void +suite:: +operator()(runner& r) +{ + *p_this_suite() = this; + try + { + run(r); + *p_this_suite() = nullptr; + } + catch(...) + { + *p_this_suite() = nullptr; + throw; + } +} + +template +bool +suite:: +expect( + Condition const& shouldBeTrue, String const& reason) +{ + if(shouldBeTrue) + { + pass(); + return true; + } + fail(reason); + return false; +} + +template +bool +suite:: +expect(Condition const& shouldBeTrue, + String const& reason, char const* file, int line) +{ + if(shouldBeTrue) + { + pass(); + return true; + } + fail(detail::make_reason(reason, file, line)); + return false; +} + +// DEPRECATED + +template +bool +suite:: +except(F&& f, String const& reason) +{ + try + { + f(); + fail(reason); + return false; + } + catch(...) + { + pass(); + } + return true; +} + +template +bool +suite:: +except(F&& f, String const& reason) +{ + try + { + f(); + fail(reason); + return false; + } + catch(E const&) + { + pass(); + } + return true; +} + +template +bool +suite:: +unexcept(F&& f, String const& reason) +{ + try + { + f(); + pass(); + return true; + } + catch(...) + { + fail(reason); + } + return false; +} + +template +bool +suite:: +unexpected( + Condition shouldBeFalse, String const& reason) +{ + bool const b = + static_cast(shouldBeFalse); + if(! b) + pass(); + else + fail(reason); + return ! b; +} + +template +void +suite:: +pass() +{ + propagate_abort(); + runner_->pass(); +} + +// ::fail +template +void +suite:: +fail(std::string const& reason) +{ + propagate_abort(); + runner_->fail(reason); + if(abort_) + { + aborted_ = true; + BOOST_THROW_EXCEPTION(abort_exception()); + } +} + +template +void +suite:: +fail(String const& reason, char const* file, int line) +{ + fail(detail::make_reason(reason, file, line)); +} + +inline +void +suite:: +propagate_abort() +{ + if(abort_ && aborted_) + BOOST_THROW_EXCEPTION(abort_exception()); +} + +template +void +suite:: +run(runner& r) +{ + runner_ = &r; + + try + { + run(); + } + catch(abort_exception const&) + { + // ends the suite + } + catch(std::exception const& e) + { + runner_->fail("unhandled exception: " + + std::string(e.what())); + } + catch(...) + { + runner_->fail("unhandled exception"); + } +} + +#ifndef BEAST_EXPECT +/** Check a precondition. + + If the condition is false, the file and line number are reported. +*/ +#define BEAST_EXPECT(cond) expect(cond, __FILE__, __LINE__) +#endif + +#ifndef BEAST_EXPECTS +/** Check a precondition. + + If the condition is false, the file and line number are reported. +*/ +#define BEAST_EXPECTS(cond, reason) ((cond) ? (pass(), true) : \ + (fail((reason), __FILE__, __LINE__), false)) +#endif + +} // unit_test +} // beast +} // boost + +//------------------------------------------------------------------------------ + +// detail: +// This inserts the suite with the given manual flag +#define BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,manual) \ + static beast::unit_test::detail::insert_suite \ + Library ## Module ## Class ## _test_instance( \ + #Class, #Module, #Library, manual) + +//------------------------------------------------------------------------------ + +// Preprocessor directives for controlling unit test definitions. + +// If this is already defined, don't redefine it. This allows +// programs to provide custom behavior for testsuite definitions +// +#ifndef BEAST_DEFINE_TESTSUITE + +/** Enables insertion of test suites into the global container. + The default is to insert all test suite definitions into the global + container. If BEAST_DEFINE_TESTSUITE is user defined, this macro + has no effect. +*/ +#ifndef BEAST_NO_UNIT_TEST_INLINE +#define BEAST_NO_UNIT_TEST_INLINE 0 +#endif + +/** Define a unit test suite. + + Library Identifies the library. + Module Identifies the module. + Class The type representing the class being tested. + + The declaration for the class implementing the test should be the same + as Class ## _test. For example, if Class is aged_ordered_container, the + test class must be declared as: + + @code + + struct aged_ordered_container_test : beast::unit_test::suite + { + //... + }; + + @endcode + + The macro invocation must appear in the same namespace as the test class. +*/ + +#if BEAST_NO_UNIT_TEST_INLINE +#define BEAST_DEFINE_TESTSUITE(Class,Module,Library) + +#else +#include +#define BEAST_DEFINE_TESTSUITE(Library,Module,Class) \ + BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,false) +#define BEAST_DEFINE_TESTSUITE_MANUAL(Library,Module,Class) \ + BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,true) + +#endif + +#endif + +//------------------------------------------------------------------------------ + +#endif diff --git a/include/boost/beast/unit_test/suite_info.hpp b/include/boost/beast/unit_test/suite_info.hpp new file mode 100644 index 00000000..83662b1c --- /dev/null +++ b/include/boost/beast/unit_test/suite_info.hpp @@ -0,0 +1,126 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_SUITE_INFO_HPP +#define BOOST_BEAST_UNIT_TEST_SUITE_INFO_HPP + +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +class runner; + +/** Associates a unit test type with metadata. */ +class suite_info +{ + using run_type = std::function; + + std::string name_; + std::string module_; + std::string library_; + bool manual_; + run_type run_; + +public: + suite_info( + std::string name, + std::string module, + std::string library, + bool manual, + run_type run) + : name_(std::move(name)) + , module_(std::move(module)) + , library_(std::move(library)) + , manual_(manual) + , run_(std::move(run)) + { + } + + std::string const& + name() const + { + return name_; + } + + std::string const& + module() const + { + return module_; + } + + std::string const& + library() const + { + return library_; + } + + /// Returns `true` if this suite only runs manually. + bool + manual() const + { + return manual_; + } + + /// Return the canonical suite name as a string. + std::string + full_name() const + { + return library_ + "." + module_ + "." + name_; + } + + /// Run a new instance of the associated test suite. + void + run(runner& r) const + { + run_(r); + } + + friend + bool + operator<(suite_info const& lhs, suite_info const& rhs) + { + return + std::tie(lhs.library_, lhs.module_, lhs.name_) < + std::tie(rhs.library_, rhs.module_, rhs.name_); + } +}; + +//------------------------------------------------------------------------------ + +/// Convenience for producing suite_info for a given test type. +template +suite_info +make_suite_info( + std::string name, + std::string module, + std::string library, + bool manual) +{ + return suite_info( + std::move(name), + std::move(module), + std::move(library), + manual, + [](runner& r) + { + Suite{}(r); + } + ); +} + +} // unit_test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/unit_test/suite_list.hpp b/include/boost/beast/unit_test/suite_list.hpp new file mode 100644 index 00000000..ee399e2f --- /dev/null +++ b/include/boost/beast/unit_test/suite_list.hpp @@ -0,0 +1,81 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_SUITE_LIST_HPP +#define BOOST_BEAST_UNIT_TEST_SUITE_LIST_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +/// A container of test suites. +class suite_list + : public detail::const_container > +{ +private: +#ifndef NDEBUG + std::unordered_set names_; + std::unordered_set classes_; +#endif + +public: + /** Insert a suite into the set. + + The suite must not already exist. + */ + template + void + insert( + char const* name, + char const* module, + char const* library, + bool manual); +}; + +//------------------------------------------------------------------------------ + +template +void +suite_list::insert( + char const* name, + char const* module, + char const* library, + bool manual) +{ +#ifndef NDEBUG + { + std::string s; + s = std::string(library) + "." + module + "." + name; + auto const result(names_.insert(s)); + BOOST_ASSERT(result.second); // Duplicate name + } + + { + auto const result(classes_.insert( + std::type_index(typeid(Suite)))); + BOOST_ASSERT(result.second); // Duplicate type + } +#endif + cont().emplace(make_suite_info( + name, module, library, manual)); +} + +} // unit_test +} // beast +} // boost + +#endif + diff --git a/include/boost/beast/unit_test/thread.hpp b/include/boost/beast/unit_test/thread.hpp new file mode 100644 index 00000000..2967f2be --- /dev/null +++ b/include/boost/beast/unit_test/thread.hpp @@ -0,0 +1,128 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_UNIT_TEST_THREAD_HPP +#define BOOST_BEAST_UNIT_TEST_THREAD_HPP + +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace unit_test { + +/** Replacement for std::thread that handles exceptions in unit tests. */ +class thread +{ +private: + suite* s_ = nullptr; + std::thread t_; + +public: + using id = std::thread::id; + using native_handle_type = std::thread::native_handle_type; + + thread() = default; + thread(thread const&) = delete; + thread& operator=(thread const&) = delete; + + thread(thread&& other) + : s_(other.s_) + , t_(std::move(other.t_)) + { + } + + thread& operator=(thread&& other) + { + s_ = other.s_; + t_ = std::move(other.t_); + return *this; + } + + template + explicit + thread(suite& s, F&& f, Args&&... args) + : s_(&s) + { + std::function b = + std::bind(std::forward(f), + std::forward(args)...); + t_ = std::thread(&thread::run, this, + std::move(b)); + } + + bool + joinable() const + { + return t_.joinable(); + } + + std::thread::id + get_id() const + { + return t_.get_id(); + } + + static + unsigned + hardware_concurrency() noexcept + { + return std::thread::hardware_concurrency(); + } + + void + join() + { + t_.join(); + s_->propagate_abort(); + } + + void + detach() + { + t_.detach(); + } + + void + swap(thread& other) + { + std::swap(s_, other.s_); + std::swap(t_, other.t_); + } + +private: + void + run(std::function f) + { + try + { + f(); + } + catch(suite::abort_exception const&) + { + } + catch(std::exception const& e) + { + s_->fail("unhandled exception: " + + std::string(e.what())); + } + catch(...) + { + s_->fail("unhandled exception"); + } + } +}; + +} // unit_test +} // beast +} // boost + +#endif