// // Copyright (c) 2019-2021 Kris Jusiak (kris at jusiak dot net) // // 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) // #if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) export module boost.ut; export import std; #else #pragma once #endif #if __has_include() #include // and, or, not, ... #endif #if not defined(__cpp_rvalue_references) #error "[Boost::ext].UT requires support for rvalue references"; #elif not defined(__cpp_decltype) #error "[Boost::ext].UT requires support for decltype"; #elif not defined(__cpp_return_type_deduction) #error "[Boost::ext].UT requires support for return type deduction"; #elif not defined(__cpp_deduction_guides) #error "[Boost::ext].UT requires support for return deduction guides"; #elif not defined(__cpp_generic_lambdas) #error "[Boost::ext].UT requires support for generic lambdas"; #elif not defined(__cpp_constexpr) #error "[Boost::ext].UT requires support for constexpr"; #elif not defined(__cpp_alias_templates) #error "[Boost::ext].UT requires support for alias templates"; #elif not defined(__cpp_variadic_templates) #error "[Boost::ext].UT requires support for variadic templates"; #elif not defined(__cpp_fold_expressions) #error "[Boost::ext].UT requires support for return fold expressions"; #elif not defined(__cpp_static_assert) #error "[Boost::ext].UT requires support for static assert"; #else #define BOOST_UT_VERSION 1'1'9 #if defined(__has_builtin) and defined(__GNUC__) and (__GNUC__ < 10) and \ not defined(__clang__) #undef __has_builtin #endif #if not defined(__has_builtin) #if defined(__GNUC__) and (__GNUC__ >= 9) #define __has___builtin_FILE 1 #define __has___builtin_LINE 1 #endif #define __has_builtin(...) __has_##__VA_ARGS__ #endif #include #include #include #include #include #include #include #if __has_include() and __has_include() #include #include #endif #if defined(__cpp_exceptions) #include #endif #if defined(__cpp_lib_source_location) #include #endif #if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) export #endif namespace boost::inline ext::ut::inline v1_1_9 { namespace utility { template class function; template class function { public: constexpr function() = default; template constexpr /*explicit(false)*/ function(T data) : invoke_{invoke_impl}, destroy_{destroy_impl}, data_{new T{static_cast(data)}} {} constexpr function(function&& other) noexcept : invoke_{static_cast(other.invoke_)}, destroy_{static_cast(other.destroy_)}, data_{static_cast(other.data_)} { other.data_ = {}; } constexpr function(const function&) = delete; ~function() { destroy_(data_); } constexpr function& operator=(const function&) = delete; constexpr function& operator=(function&&) = delete; [[nodiscard]] constexpr auto operator()(TArgs... args) -> R { return invoke_(data_, args...); } [[nodiscard]] constexpr auto operator()(TArgs... args) const -> R { return invoke_(data_, args...); } private: template [[nodiscard]] static auto invoke_impl(void* data, TArgs... args) -> R { return (*static_cast(data))(args...); } template static auto destroy_impl(void* data) -> void { delete static_cast(data); } R (*invoke_)(void*, TArgs...){}; void (*destroy_)(void*){}; void* data_{}; }; [[nodiscard]] inline auto is_match(std::string_view input, std::string_view pattern) -> bool { if (std::empty(pattern)) { return std::empty(input); } if (std::empty(input)) { return pattern[0] == '*' ? is_match(input, pattern.substr(1)) : false; } if (pattern[0] != '?' and pattern[0] != '*' and pattern[0] != input[0]) { return false; } if (pattern[0] == '*') { for (decltype(std::size(input)) i = 0u; i <= std::size(input); ++i) { if (is_match(input.substr(i), pattern.substr(1))) { return true; } } return false; } return is_match(input.substr(1), pattern.substr(1)); } template [[nodiscard]] constexpr auto match(const TPattern& pattern, const TStr& str) -> std::vector { std::vector groups{}; auto pi = 0u; auto si = 0u; const auto matcher = [&](char b, char e, char c = 0) { const auto match = si; while (str[si] and str[si] != b and str[si] != c) { ++si; } groups.emplace_back(str.substr(match, si - match)); while (pattern[pi] and pattern[pi] != e) { ++pi; } pi++; }; while (pi < std::size(pattern) && si < std::size(str)) { if (pattern[pi] == '\'' and str[si] == '\'' and pattern[pi + 1] == '{') { ++si; matcher('\'', '}'); } else if (pattern[pi] == '{') { matcher(' ', '}', ','); } else if (pattern[pi] != str[si]) { return {}; } ++pi; ++si; } if (si < str.size() or pi < std::size(pattern)) { return {}; } return groups; } template [[nodiscard]] inline auto split(T input, TDelim delim) -> std::vector { std::vector output{}; std::size_t first{}; while (first < std::size(input)) { const auto second = input.find_first_of(delim, first); if (first != second) { output.emplace_back(input.substr(first, second - first)); } if (second == T::npos) { break; } first = second + 1; } return output; } } // namespace utility namespace reflection { #if defined(__cpp_lib_source_location) using source_location = std::source_location; #else class source_location { public: [[nodiscard]] static constexpr auto current( #if (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE)) const char* file = __builtin_FILE(), int line = __builtin_LINE() #else const char* file = "unknown", int line = {} #endif ) noexcept { source_location sl{}; sl.file_ = file; sl.line_ = line; return sl; } [[nodiscard]] constexpr auto file_name() const noexcept { return file_; } [[nodiscard]] constexpr auto line() const noexcept { return line_; } private: const char* file_{"unknown"}; int line_{}; }; #endif template [[nodiscard]] constexpr auto type_name() -> std::string_view { #if defined(_MSC_VER) and not defined(__clang__) return {&__FUNCSIG__[120], sizeof(__FUNCSIG__) - 128}; #elif defined(__clang_analyzer__) return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; #elif defined(__clang__) and (__clang_major__ >= 13) and defined(__APPLE__) return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; #elif defined(__clang__) and (__clang_major__ >= 12) and not defined(__APPLE__) return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; #elif defined(__clang__) return {&__PRETTY_FUNCTION__[70], sizeof(__PRETTY_FUNCTION__) - 72}; #elif defined(__GNUC__) return {&__PRETTY_FUNCTION__[85], sizeof(__PRETTY_FUNCTION__) - 136}; #endif } } // namespace reflection namespace math { template [[nodiscard]] constexpr auto abs(const T t) -> T { return t < T{} ? -t : t; } template [[nodiscard]] constexpr auto min_value(const T& lhs, const T& rhs) -> const T& { return (rhs < lhs) ? rhs : lhs; } template [[nodiscard]] constexpr auto pow(const T base, const TExp exp) -> T { return exp ? T(base * pow(base, exp - TExp(1))) : T(1); } template [[nodiscard]] constexpr auto num() -> T { static_assert( ((Cs == '.' or Cs == '\'' or (Cs >= '0' and Cs <= '9')) and ...)); T result{}; for (const char c : {Cs...}) { if (c == '.') { break; } if (c >= '0' and c <= '9') { result = result * T(10) + T(c - '0'); } } return result; } template [[nodiscard]] constexpr auto den() -> T { constexpr const std::array cs{Cs...}; T result{}; auto i = 0u; while (cs[i++] != '.') { } for (auto j = i; j < sizeof...(Cs); ++j) { result += pow(T(10), sizeof...(Cs) - j) * T(cs[j] - '0'); } return result; } template [[nodiscard]] constexpr auto den_size() -> T { constexpr const std::array cs{Cs...}; T i{}; while (cs[i++] != '.') { } return T(sizeof...(Cs)) - i + T(1); } template [[nodiscard]] constexpr auto den_size(TValue value) -> T { constexpr auto precision = TValue(1e-7); T result{}; TValue tmp{}; do { value *= 10; tmp = value - T(value); ++result; } while (tmp > precision); return result; } } // namespace math namespace type_traits { template struct list {}; template struct identity { using type = T; }; template struct function_traits : function_traits {}; template struct function_traits { using result_type = R; using args = list; }; template struct function_traits { using result_type = R; using args = list; }; template struct function_traits { using result_type = R; using args = list; }; template struct function_traits { using result_type = R; using args = list; }; template T&& declval(); template constexpr auto is_valid(TExpr expr) -> decltype(expr(declval()), bool()) { return true; } template constexpr auto is_valid(...) -> bool { return false; } template static constexpr auto is_container_v = is_valid([](auto t) -> decltype(t.begin(), t.end(), void()) {}); template static constexpr auto has_user_print = is_valid([](auto t) -> decltype(void(declval() << t)) {}); template static constexpr auto has_value_v = is_valid([](auto t) -> decltype(void(t.value)) {}); template static constexpr auto has_epsilon_v = is_valid([](auto t) -> decltype(void(t.epsilon)) {}); template inline constexpr auto is_floating_point_v = false; template <> inline constexpr auto is_floating_point_v = true; template <> inline constexpr auto is_floating_point_v = true; template <> inline constexpr auto is_floating_point_v = true; #if defined(__clang__) or defined(_MSC_VER) template static constexpr auto is_convertible_v = __is_convertible_to(From, To); #else template constexpr auto is_convertible(int) -> decltype(bool(To(declval()))) { return true; } template constexpr auto is_convertible(...) { return false; } template constexpr auto is_convertible_v = is_convertible(0); #endif template struct requires_ {}; template <> struct requires_ { using type = int; }; template using requires_t = typename requires_::type; } // namespace type_traits struct none {}; namespace events { struct test_begin { std::string_view type{}; std::string_view name{}; reflection::source_location location{}; }; template struct test { std::string_view type{}; std::string_view name{}; std::vector tag{}; reflection::source_location location{}; TArg arg{}; Test run{}; constexpr auto operator()() { run_impl(static_cast(run), arg); } constexpr auto operator()() const { run_impl(static_cast(run), arg); } private: static constexpr auto run_impl(Test test, const none&) { test(); } template static constexpr auto run_impl(T test, const TArg& arg) -> decltype(test(arg), void()) { test(arg); } template static constexpr auto run_impl(T test, const TArg&) -> decltype(test.template operator()(), void()) { test.template operator()(); } }; template test(std::string_view, std::string_view, std::string_view, reflection::source_location, TArg, Test) -> test; template struct suite { TSuite run{}; constexpr auto operator()() { run(); } constexpr auto operator()() const { run(); } }; template suite(TSuite) -> suite; struct test_run { std::string_view type{}; std::string_view name{}; }; template struct skip { std::string_view type{}; std::string_view name{}; TArg arg{}; }; template skip(std::string_view, std::string_view, TArg) -> skip; struct test_skip { std::string_view type{}; std::string_view name{}; }; template struct assertion { TExpr expr{}; reflection::source_location location{}; }; template assertion(TExpr, reflection::source_location) -> assertion; template struct assertion_pass { TExpr expr{}; reflection::source_location location{}; }; template assertion_pass(TExpr) -> assertion_pass; template struct assertion_fail { TExpr expr{}; reflection::source_location location{}; }; template assertion_fail(TExpr) -> assertion_fail; struct test_end { std::string_view type{}; std::string_view name{}; }; template struct log { TMsg msg{}; }; template log(TMsg) -> log; struct fatal_assertion {}; struct exception { const char* msg{}; [[nodiscard]] auto what() const -> const char* { return msg; } }; struct summary {}; } // namespace events namespace detail { struct op {}; struct fatal {}; struct cfg { static inline reflection::source_location location{}; static inline bool wip{}; }; template [[nodiscard]] constexpr auto get_impl(const T& t, int) -> decltype(t.get()) { return t.get(); } template [[nodiscard]] constexpr auto get_impl(const T& t, ...) -> decltype(auto) { return t; } template [[nodiscard]] constexpr auto get(const T& t) { return get_impl(t, 0); } template struct type_ : op { template [[nodiscard]] constexpr auto operator()(const TOther&) const -> const type_ { return {}; } [[nodiscard]] constexpr auto operator==(type_) -> bool { return true; } template [[nodiscard]] constexpr auto operator==(type_) -> bool { return false; } template [[nodiscard]] constexpr auto operator==(const TOther&) -> bool { return std::is_same_v; } [[nodiscard]] constexpr auto operator!=(type_) -> bool { return true; } template [[nodiscard]] constexpr auto operator!=(type_) -> bool { return true; } template [[nodiscard]] constexpr auto operator!=(const TOther&) -> bool { return not std::is_same_v; } }; template struct value : op { using value_type = T; constexpr /*explicit(false)*/ value(const T& _value) : value_{_value} {} [[nodiscard]] constexpr explicit operator T() const { return value_; } [[nodiscard]] constexpr decltype(auto) get() const { return value_; } T value_{}; }; template struct value>> : op { using value_type = T; static inline auto epsilon = T{}; constexpr value(const T& _value, const T precision) : value_{_value} { epsilon = precision; } constexpr /*explicit(false)*/ value(const T& val) : value{val, T(1) / math::pow(T(10), math::den_size(val))} {} [[nodiscard]] constexpr explicit operator T() const { return value_; } [[nodiscard]] constexpr decltype(auto) get() const { return value_; } T value_{}; }; template class value_location : public detail::value { public: constexpr /*explicit(false)*/ value_location( const T& t, const reflection::source_location& sl = reflection::source_location::current()) : detail::value{t} { cfg::location = sl; } constexpr value_location(const T& t, const T precision, const reflection::source_location& sl = reflection::source_location::current()) : detail::value{t, precision} { cfg::location = sl; } }; template struct integral_constant : op { using value_type = decltype(N); static constexpr auto value = N; [[nodiscard]] constexpr auto operator-() const { return integral_constant<-N>{}; } [[nodiscard]] constexpr explicit operator value_type() const { return N; } [[nodiscard]] constexpr auto get() const { return N; } }; template struct floating_point_constant : op { using value_type = T; static constexpr auto epsilon = T(1) / math::pow(T(10), Size - 1); static constexpr auto value = T(P) * (T(N) + (T(D) / math::pow(T(10), Size))); [[nodiscard]] constexpr auto operator-() const { return floating_point_constant{}; } [[nodiscard]] constexpr explicit operator value_type() const { return value; } [[nodiscard]] constexpr auto get() const { return value; } }; template struct eq_ : op { constexpr eq_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator==; using std::operator<; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value == TRhs::value; } else if constexpr (type_traits::has_epsilon_v and type_traits::has_epsilon_v) { return math::abs(get(lhs) - get(rhs)) < math::min_value(TLhs::epsilon, TRhs::epsilon); } else if constexpr (type_traits::has_epsilon_v) { return math::abs(get(lhs) - get(rhs)) < TLhs::epsilon; } else if constexpr (type_traits::has_epsilon_v) { return math::abs(get(lhs) - get(rhs)) < TRhs::epsilon; } else { return get(lhs) == get(rhs); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct neq_ : op { constexpr neq_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator==; using std::operator!=; using std::operator>; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value != TRhs::value; } else if constexpr (type_traits::has_epsilon_v and type_traits::has_epsilon_v) { return math::abs(get(lhs_) - get(rhs_)) > math::min_value(TLhs::epsilon, TRhs::epsilon); } else if constexpr (type_traits::has_epsilon_v) { return math::abs(get(lhs_) - get(rhs_)) > TLhs::epsilon; } else if constexpr (type_traits::has_epsilon_v) { return math::abs(get(lhs_) - get(rhs_)) > TRhs::epsilon; } else { return get(lhs_) != get(rhs_); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct gt_ : op { constexpr gt_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator>; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value > TRhs::value; } else { return get(lhs_) > get(rhs_); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct ge_ : op { constexpr ge_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator>=; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value >= TRhs::value; } else { return get(lhs_) >= get(rhs_); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct lt_ : op { constexpr lt_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator<; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value < TRhs::value; } else { return get(lhs_) < get(rhs_); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } private: const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct le_ : op { constexpr le_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{[&] { using std::operator<=; if constexpr (type_traits::has_value_v and type_traits::has_value_v) { return TLhs::value <= TRhs::value; } else { return get(lhs_) <= get(rhs_); } }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct and_ : op { constexpr and_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{static_cast(lhs) and static_cast(rhs)} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct or_ : op { constexpr or_(const TLhs& lhs = {}, const TRhs& rhs = {}) : lhs_{lhs}, rhs_{rhs}, value_{static_cast(lhs) or static_cast(rhs)} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } const TLhs lhs_{}; const TRhs rhs_{}; const bool value_{}; }; template struct not_ : op { explicit constexpr not_(const T& t = {}) : t_{t}, value_{not static_cast(t)} {} [[nodiscard]] constexpr operator bool() const { return value_; } [[nodiscard]] constexpr auto value() const { return get(t_); } const T t_{}; const bool value_{}; }; template struct fatal_; #if defined(__cpp_exceptions) template struct throws_ : op { constexpr explicit throws_(const TExpr& expr) : value_{[&expr] { try { expr(); } catch (const TException&) { return true; } catch (...) { return false; } return false; }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } const bool value_{}; }; template struct throws_ : op { constexpr explicit throws_(const TExpr& expr) : value_{[&expr] { try { expr(); } catch (...) { return true; } return false; }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } const bool value_{}; }; template struct nothrow_ : op { constexpr explicit nothrow_(const TExpr& expr) : value_{[&expr] { try { expr(); } catch (...) { return false; } return true; }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } const bool value_{}; }; #endif #if __has_include() and __has_include() template struct aborts_ : op { constexpr explicit aborts_(const TExpr& expr) : value_{[&expr]() -> bool { if (const auto pid = fork(); not pid) { expr(); exit(0); } auto exit_status = 0; wait(&exit_status); return exit_status; }()} {} [[nodiscard]] constexpr operator bool() const { return value_; } const bool value_{}; }; #endif } // namespace detail namespace type_traits { template inline constexpr auto is_op_v = __is_base_of(detail::op, T); } // namespace type_traits struct colors { std::string_view none = "\033[0m"; std::string_view pass = "\033[32m"; std::string_view fail = "\033[31m"; }; class printer { [[nodiscard]] inline auto color(const bool cond) { return cond ? colors_.pass : colors_.fail; } public: printer() = default; /*explicit(false)*/ printer(const colors colors) : colors_{colors} {} template auto& operator<<(const T& t) { out_ << detail::get(t); return *this; } template and type_traits::is_container_v> = 0> auto& operator<<(T&& t) { *this << '{'; auto first = true; for (const auto& arg : t) { *this << (first ? "" : ", ") << arg; first = false; } *this << '}'; return *this; } auto& operator<<(std::string_view sv) { out_ << sv; return *this; } template auto& operator<<(const detail::eq_& op) { return (*this << color(op) << op.lhs() << " == " << op.rhs() << colors_.none); } template auto& operator<<(const detail::neq_& op) { return (*this << color(op) << op.lhs() << " != " << op.rhs() << colors_.none); } template auto& operator<<(const detail::gt_& op) { return (*this << color(op) << op.lhs() << " > " << op.rhs() << colors_.none); } template auto& operator<<(const detail::ge_& op) { return (*this << color(op) << op.lhs() << " >= " << op.rhs() << colors_.none); } template auto& operator<<(const detail::lt_& op) { return (*this << color(op) << op.lhs() << " < " << op.rhs() << colors_.none); } template auto& operator<<(const detail::le_& op) { return (*this << color(op) << op.lhs() << " <= " << op.rhs() << colors_.none); } template auto& operator<<(const detail::and_& op) { return (*this << '(' << op.lhs() << color(op) << " and " << colors_.none << op.rhs() << ')'); } template auto& operator<<(const detail::or_& op) { return (*this << '(' << op.lhs() << color(op) << " or " << colors_.none << op.rhs() << ')'); } template auto& operator<<(const detail::not_& op) { return (*this << color(op) << "not " << op.value() << colors_.none); } template auto& operator<<(const detail::fatal_& fatal) { return (*this << fatal.get()); } #if defined(__cpp_exceptions) template auto& operator<<(const detail::throws_& op) { return (*this << color(op) << "throws<" << reflection::type_name() << ">" << colors_.none); } template auto& operator<<(const detail::throws_& op) { return (*this << color(op) << "throws" << colors_.none); } template auto& operator<<(const detail::nothrow_& op) { return (*this << color(op) << "nothrow" << colors_.none); } #endif #if __has_include() and __has_include() template auto& operator<<(const detail::aborts_& op) { return (*this << color(op) << "aborts" << colors_.none); } #endif template auto& operator<<(const detail::type_&) { return (*this << reflection::type_name()); } auto str() const { return out_.str(); } const auto& colors() const { return colors_; } private: ut::colors colors_{}; std::ostringstream out_{}; }; template class reporter { public: constexpr auto operator=(TPrinter printer) { printer_ = static_cast(printer); } auto on(events::test_begin test_begin) -> void { printer_ << "Running \"" << test_begin.name << "\"..."; fails_ = asserts_.fail; } auto on(events::test_run test_run) -> void { printer_ << "\n \"" << test_run.name << "\"..."; } auto on(events::test_skip test_skip) -> void { printer_ << test_skip.name << "...SKIPPED\n"; ++tests_.skip; } auto on(events::test_end) -> void { if (asserts_.fail > fails_) { ++tests_.fail; printer_ << '\n' << printer_.colors().fail << "FAILED" << printer_.colors().none << '\n'; } else { ++tests_.pass; printer_ << printer_.colors().pass << "PASSED" << printer_.colors().none << '\n'; } } template auto on(events::log l) -> void { printer_ << l.msg; } auto on(events::exception exception) -> void { printer_ << "\n " << printer_.colors().fail << "Unexpected exception with message:\n" << exception.what() << printer_.colors().none; ++asserts_.fail; } template auto on(events::assertion_pass) -> void { ++asserts_.pass; } template auto on(events::assertion_fail assertion) -> void { constexpr auto short_name = [](std::string_view name) { return name.rfind('/') != std::string_view::npos ? name.substr(name.rfind('/') + 1) : name; }; printer_ << "\n " << short_name(assertion.location.file_name()) << ':' << assertion.location.line() << ':' << printer_.colors().fail << "FAILED" << printer_.colors().none << " [" << std::boolalpha << assertion.expr << printer_.colors().none << ']'; ++asserts_.fail; } auto on(events::fatal_assertion) -> void {} auto on(events::summary) -> void { if (static auto once = true; once) { once = false; if (tests_.fail or asserts_.fail) { printer_ << "\n========================================================" "=======================\n" << "tests: " << (tests_.pass + tests_.fail) << " | " << printer_.colors().fail << tests_.fail << " failed" << printer_.colors().none << '\n' << "asserts: " << (asserts_.pass + asserts_.fail) << " | " << asserts_.pass << " passed" << " | " << printer_.colors().fail << asserts_.fail << " failed" << printer_.colors().none << '\n'; std::cerr << printer_.str() << std::endl; } else { std::cout << printer_.colors().pass << "All tests passed" << printer_.colors().none << " (" << asserts_.pass << " asserts in " << tests_.pass << " tests)\n"; if (tests_.skip) { std::cout << tests_.skip << " tests skipped\n"; } std::cout.flush(); } } } protected: struct { std::size_t pass{}; std::size_t fail{}; std::size_t skip{}; } tests_{}; struct { std::size_t pass{}; std::size_t fail{}; } asserts_{}; std::size_t fails_{}; TPrinter printer_{}; }; struct options { std::string_view filter{}; std::vector tag{}; ut::colors colors{}; bool dry_run{}; }; struct run_cfg { bool report_errors{false}; }; template , auto MaxPathSize = 16> class runner { class filter { static constexpr auto delim = "."; public: constexpr /*explicit(false)*/ filter(std::string_view _filter = {}) : path_{utility::split(_filter, delim)} {} template constexpr auto operator()(const std::size_t level, const TPath& path) const -> bool { for (auto i = 0u; i < math::min_value(level + 1, std::size(path_)); ++i) { if (not utility::is_match(path[i], path_[i])) { return false; } } return true; } private: std::vector path_{}; }; public: constexpr runner() = default; constexpr runner(TReporter reporter, std::size_t suites_size) : reporter_{std::move(reporter)}, suites_(suites_size) {} ~runner() { const auto should_run = not run_; if (should_run) { static_cast(run()); } if (not dry_run_) { reporter_.on(events::summary{}); } if (should_run and fails_) { std::exit(-1); } } auto operator=(const options& options) { filter_ = options.filter; tag_ = options.tag; dry_run_ = options.dry_run; reporter_ = {options.colors}; } template auto on(events::suite suite) { suites_.push_back(suite.run); } template auto on(events::test test) { path_[level_] = test.name; auto execute = std::empty(test.tag); for (const auto& tag_element : test.tag) { if (utility::is_match(tag_element, "skip")) { on(events::skip<>{.type = test.type, .name = test.name}); return; } for (const auto& ftag : tag_) { if (utility::is_match(tag_element, ftag)) { execute = true; break; } } } if (not execute) { on(events::skip<>{.type = test.type, .name = test.name}); return; } if (filter_(level_, path_)) { if (not level_++) { reporter_.on(events::test_begin{ .type = test.type, .name = test.name, .location = test.location}); } else { reporter_.on(events::test_run{.type = test.type, .name = test.name}); } if (dry_run_) { for (auto i = 0u; i < level_; ++i) { std::cout << (i ? "." : "") << path_[i]; } std::cout << '\n'; } #if defined(__cpp_exceptions) try { #endif test(); #if defined(__cpp_exceptions) } catch (const events::fatal_assertion&) { } catch (const std::exception& exception) { ++fails_; reporter_.on(events::exception{exception.what()}); } catch (...) { ++fails_; reporter_.on(events::exception{"Unknown exception"}); } #endif if (not --level_) { reporter_.on(events::test_end{.type = test.type, .name = test.name}); } } } template auto on(events::skip test) { reporter_.on(events::test_skip{.type = test.type, .name = test.name}); } template [[nodiscard]] auto on(events::assertion assertion) -> bool { if (dry_run_) { return true; } if (static_cast(assertion.expr)) { reporter_.on(events::assertion_pass{ .expr = assertion.expr, .location = assertion.location}); return true; } ++fails_; reporter_.on(events::assertion_fail{.expr = assertion.expr, .location = assertion.location}); return false; } auto on(events::fatal_assertion fatal_assertion) { reporter_.on(fatal_assertion); #if defined(__cpp_exceptions) if (not level_) { reporter_.on(events::summary{}); } throw fatal_assertion; #else if (level_) { reporter_.on(events::test_end{}); } reporter_.on(events::summary{}); std::abort(); #endif } template auto on(events::log l) { reporter_.on(l); } [[nodiscard]] auto run(run_cfg rc = {}) -> bool { run_ = true; for (const auto& suite : suites_) { suite(); } suites_.clear(); if (rc.report_errors) { reporter_.on(events::summary{}); } return fails_ > 0; } protected: TReporter reporter_{}; std::vector suites_{}; std::size_t level_{}; bool run_{}; std::size_t fails_{}; std::array path_{}; filter filter_{}; std::vector tag_{}; bool dry_run_{}; }; struct override {}; template [[maybe_unused]] inline auto cfg = runner>{}; namespace detail { struct tag { std::vector name{}; }; template [[nodiscard]] constexpr decltype(auto) on(TEvent&& event) { return ut::cfg::type>.on( static_cast(event)); } template struct test_location { template constexpr test_location(const T& t, const reflection::source_location& sl = reflection::source_location::current()) : test{t}, location{sl} {} Test test{}; reflection::source_location location{}; }; struct test { std::string_view type{}; std::string_view name{}; std::vector tag{}; template constexpr auto operator=(test_location _test) { on(events::test{.type = type, .name = name, .tag = tag, .location = _test.location, .arg = none{}, .run = _test.test}); return _test.test; } template > = 0> constexpr auto operator=(Test _test) -> typename type_traits::identity::type { on(events::test{.type = type, .name = name, .tag = tag, .location = {}, .arg = none{}, .run = static_cast(_test)}); return _test; } constexpr auto operator=(void (*_test)(std::string_view)) const { return _test(name); } template > = 0> constexpr auto operator=(Test _test) -> decltype(_test(type_traits::declval())) { return _test(name); } }; struct log { struct next { template auto& operator<<(const TMsg& msg) { on(events::log{' '}); on(events::log{msg}); return *this; } }; template auto operator<<(const TMsg& msg) -> next { on(events::log{'\n'}); on(events::log{msg}); return next{}; } }; template class terse_ { public: constexpr explicit terse_(const TExpr& expr) : expr_{expr} { cfg::wip = {}; } ~terse_() noexcept(false) { if (static auto once = true; once and not cfg::wip) { once = {}; } else { return; } cfg::wip = true; void(detail::on( events::assertion{.expr = expr_, .location = cfg::location})); } private: const TExpr& expr_; }; struct that_ { template struct expr { using type = expr; constexpr explicit expr(const T& t) : t_{t} {} [[nodiscard]] constexpr auto operator!() const { return not_{*this}; } template [[nodiscard]] constexpr auto operator==(const TRhs& rhs) const { return eq_{t_, rhs}; } template [[nodiscard]] constexpr auto operator!=(const TRhs& rhs) const { return neq_{t_, rhs}; } template [[nodiscard]] constexpr auto operator>(const TRhs& rhs) const { return gt_{t_, rhs}; } template [[nodiscard]] constexpr auto operator>=(const TRhs& rhs) const { return ge_{t_, rhs}; } template [[nodiscard]] constexpr auto operator<(const TRhs& rhs) const { return lt_{t_, rhs}; } template [[nodiscard]] constexpr auto operator<=(const TRhs& rhs) const { return le_{t_, rhs}; } [[nodiscard]] constexpr operator bool() const { return static_cast(t_); } const T t_{}; }; template [[nodiscard]] constexpr auto operator%(const T& t) const { return expr{t}; } }; template struct fatal_ : op { using type = fatal_; constexpr explicit fatal_(const TExpr& expr) : expr_{expr} {} [[nodiscard]] constexpr operator bool() const { if (static_cast(expr_)) { } else { cfg::wip = true; void(on( events::assertion{.expr = expr_, .location = cfg::location})); on(events::fatal_assertion{}); } return static_cast(expr_); } [[nodiscard]] constexpr decltype(auto) get() const { return expr_; } TExpr expr_{}; }; template struct expect_ { constexpr explicit expect_(bool value) : value_{value} { cfg::wip = {}; } template auto& operator<<(const TMsg& msg) { if (not value_) { on(events::log{' '}); on(events::log{msg}); } return *this; } [[nodiscard]] constexpr operator bool() const { return value_; } bool value_{}; }; } // namespace detail namespace literals { [[nodiscard]] inline auto operator""_test(const char* name, decltype(sizeof("")) size) { return detail::test{"test", std::string_view{name, size}}; } template [[nodiscard]] constexpr auto operator""_i() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_s() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_c() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_sc() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_l() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_ll() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_u() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_uc() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_us() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_ul() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_ull() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_i8() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_i16() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_i32() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_i64() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_u8() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_u16() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_u32() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_u64() { return detail::integral_constant()>{}; } template [[nodiscard]] constexpr auto operator""_f() { return detail::floating_point_constant< float, math::num(), math::den(), math::den_size()>{}; } template [[nodiscard]] constexpr auto operator""_d() { return detail::floating_point_constant< double, math::num(), math::den(), math::den_size()>{}; } template [[nodiscard]] constexpr auto operator""_ld() { return detail::floating_point_constant< long double, math::num(), math::den(), math::den_size()>{}; } constexpr auto operator""_b(const char* name, decltype(sizeof("")) size) { struct named : std::string_view, detail::op { using value_type = bool; [[nodiscard]] constexpr operator value_type() const { return true; } [[nodiscard]] constexpr auto operator==(const named&) const { return true; } [[nodiscard]] constexpr auto operator==(const bool other) const { return other; } }; return named{{name, size}, {}}; } } // namespace literals namespace operators { [[nodiscard]] constexpr auto operator==(std::string_view lhs, std::string_view rhs) { return detail::eq_{lhs, rhs}; } [[nodiscard]] constexpr auto operator!=(std::string_view lhs, std::string_view rhs) { return detail::neq_{lhs, rhs}; } template > = 0> [[nodiscard]] constexpr auto operator==(T&& lhs, T&& rhs) { return detail::eq_{static_cast(lhs), static_cast(rhs)}; } template > = 0> [[nodiscard]] constexpr auto operator!=(T&& lhs, T&& rhs) { return detail::neq_{static_cast(lhs), static_cast(rhs)}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator==(const TLhs& lhs, const TRhs& rhs) { return detail::eq_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator!=(const TLhs& lhs, const TRhs& rhs) { return detail::neq_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator>(const TLhs& lhs, const TRhs& rhs) { return detail::gt_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator>=(const TLhs& lhs, const TRhs& rhs) { return detail::ge_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator<(const TLhs& lhs, const TRhs& rhs) { return detail::lt_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator<=(const TLhs& lhs, const TRhs& rhs) { return detail::le_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { return detail::and_{lhs, rhs}; } template or type_traits::is_op_v> = 0> [[nodiscard]] constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { return detail::or_{lhs, rhs}; } template > = 0> [[nodiscard]] constexpr auto operator not(const T& t) { return detail::not_{t}; } template [[nodiscard]] inline auto operator>>( const T& t, const detail::value_location&) { return detail::fatal_{t}; } template [[nodiscard]] auto operator/(const detail::tag& tag, Test test) { for (const auto& name : tag.name) { test.tag.push_back(name); } return test; } [[nodiscard]] inline auto operator/(const detail::tag& lhs, const detail::tag& rhs) { std::vector tag{}; for (const auto& name : lhs.name) { tag.push_back(name); } for (const auto& name : rhs.name) { tag.push_back(name); } return detail::tag{tag}; } template > = 0> [[nodiscard]] constexpr auto operator|(const F& f, const T& t) { return [f, t](const auto name) { for (const auto& arg : t) { detail::on(events::test{.type = "test", .name = name, .tag = {}, .location = {}, .arg = arg, .run = f}); } }; } template < class F, template class T, class... Ts, type_traits::requires_t>> = 0> [[nodiscard]] constexpr auto operator|(const F& f, const T& t) { return [f, t](const auto name) { apply( [f, name](const auto&... args) { (detail::on(events::test{.type = "test", .name = name, .tag = {}, .location = {}, .arg = args, .run = f}), ...); }, t); }; } namespace terse { #if defined(__clang__) #pragma clang diagnostic ignored "-Wunused-comparison" #endif [[maybe_unused]] constexpr struct { } _t; template constexpr auto operator%(const T& t, const decltype(_t)&) { return detail::value{t}; } template inline auto operator>>(const T& t, const detail::value_location&) { using fatal_t = detail::fatal_; struct fatal_ : fatal_t, detail::log { using type [[maybe_unused]] = fatal_t; using fatal_t::fatal_t; const detail::terse_ _{*this}; }; return fatal_{t}; } template > = 0> constexpr auto operator==( const T& lhs, const detail::value_location& rhs) { using eq_t = detail::eq_>; struct eq_ : eq_t, detail::log { using type [[maybe_unused]] = eq_t; using eq_t::eq_t; const detail::terse_ _{*this}; }; return eq_{lhs, rhs}; } template > = 0> constexpr auto operator==( const detail::value_location& lhs, const T& rhs) { using eq_t = detail::eq_, T>; struct eq_ : eq_t, detail::log { using type [[maybe_unused]] = eq_t; using eq_t::eq_t; const detail::terse_ _{*this}; }; return eq_{lhs, rhs}; } template > = 0> constexpr auto operator!=( const T& lhs, const detail::value_location& rhs) { using neq_t = detail::neq_>; struct neq_ : neq_t, detail::log { using type [[maybe_unused]] = neq_t; using neq_t::neq_t; const detail::terse_ _{*this}; }; return neq_{lhs, rhs}; } template > = 0> constexpr auto operator!=( const detail::value_location& lhs, const T& rhs) { using neq_t = detail::neq_, T>; struct neq_ : neq_t { using type [[maybe_unused]] = neq_t; using neq_t::neq_t; const detail::terse_ _{*this}; }; return neq_{lhs, rhs}; } template > = 0> constexpr auto operator>( const T& lhs, const detail::value_location& rhs) { using gt_t = detail::gt_>; struct gt_ : gt_t, detail::log { using type [[maybe_unused]] = gt_t; using gt_t::gt_t; const detail::terse_ _{*this}; }; return gt_{lhs, rhs}; } template > = 0> constexpr auto operator>( const detail::value_location& lhs, const T& rhs) { using gt_t = detail::gt_, T>; struct gt_ : gt_t, detail::log { using type [[maybe_unused]] = gt_t; using gt_t::gt_t; const detail::terse_ _{*this}; }; return gt_{lhs, rhs}; } template > = 0> constexpr auto operator>=( const T& lhs, const detail::value_location& rhs) { using ge_t = detail::ge_>; struct ge_ : ge_t, detail::log { using type [[maybe_unused]] = ge_t; using ge_t::ge_t; const detail::terse_ _{*this}; }; return ge_{lhs, rhs}; } template > = 0> constexpr auto operator>=( const detail::value_location& lhs, const T& rhs) { using ge_t = detail::ge_, T>; struct ge_ : ge_t, detail::log { using type [[maybe_unused]] = ge_t; using ge_t::ge_t; const detail::terse_ _{*this}; }; return ge_{lhs, rhs}; } template > = 0> constexpr auto operator<( const T& lhs, const detail::value_location& rhs) { using lt_t = detail::lt_>; struct lt_ : lt_t, detail::log { using type [[maybe_unused]] = lt_t; using lt_t::lt_t; const detail::terse_ _{*this}; }; return lt_{lhs, rhs}; } template > = 0> constexpr auto operator<( const detail::value_location& lhs, const T& rhs) { using lt_t = detail::lt_, T>; struct lt_ : lt_t, detail::log { using type [[maybe_unused]] = lt_t; using lt_t::lt_t; const detail::terse_ _{*this}; }; return lt_{lhs, rhs}; } template > = 0> constexpr auto operator<=( const T& lhs, const detail::value_location& rhs) { using le_t = detail::le_>; struct le_ : le_t, detail::log { using type [[maybe_unused]] = le_t; using le_t::le_t; const detail::terse_ _{*this}; }; return le_{lhs, rhs}; } template > = 0> constexpr auto operator<=( const detail::value_location& lhs, const T& rhs) { using le_t = detail::le_, T>; struct le_ : le_t { using type [[maybe_unused]] = le_t; using le_t::le_t; const detail::terse_ _{*this}; }; return le_{lhs, rhs}; } template or type_traits::is_op_v> = 0> constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { using and_t = detail::and_; struct and_ : and_t, detail::log { using type [[maybe_unused]] = and_t; using and_t::and_t; const detail::terse_ _{*this}; }; return and_{lhs, rhs}; } template or type_traits::is_op_v> = 0> constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { using or_t = detail::or_; struct or_ : or_t, detail::log { using type [[maybe_unused]] = or_t; using or_t::or_t; const detail::terse_ _{*this}; }; return or_{lhs, rhs}; } template > = 0> constexpr auto operator not(const T& t) { using not_t = detail::not_; struct not_ : not_t, detail::log { using type [[maybe_unused]] = not_t; using not_t::not_t; const detail::terse_ _{*this}; }; return not_{t}; } } // namespace terse } // namespace operators template or type_traits::is_convertible_v> = 0> constexpr auto expect(const TExpr& expr, const reflection::source_location& sl = reflection::source_location::current()) { return detail::expect_{detail::on( events::assertion{.expr = expr, .location = sl})}; } [[maybe_unused]] constexpr auto fatal = detail::fatal{}; #if defined(__cpp_nontype_template_parameter_class) template #else template #endif constexpr auto constant = Constant; #if defined(__cpp_exceptions) template [[nodiscard]] constexpr auto throws(const TExpr& expr) { return detail::throws_{expr}; } template [[nodiscard]] constexpr auto throws(const TExpr& expr) { return detail::throws_{expr}; } template [[nodiscard]] constexpr auto nothrow(const TExpr& expr) { return detail::nothrow_{expr}; } #endif #if __has_include() and __has_include() template [[nodiscard]] constexpr auto aborts(const TExpr& expr) { return detail::aborts_{expr}; } #endif using _b = detail::value; using _c = detail::value; using _sc = detail::value; using _s = detail::value; using _i = detail::value; using _l = detail::value; using _ll = detail::value; using _u = detail::value; using _uc = detail::value; using _us = detail::value; using _ul = detail::value; using _ull = detail::value; using _i8 = detail::value; using _i16 = detail::value; using _i32 = detail::value; using _i64 = detail::value; using _u8 = detail::value; using _u16 = detail::value; using _u32 = detail::value; using _u64 = detail::value; using _f = detail::value; using _d = detail::value; using _ld = detail::value; template struct _t : detail::value { constexpr explicit _t(const T& t) : detail::value{t} {} }; struct suite { template constexpr /*explicit(false)*/ suite(TSuite _suite) { static_assert(1 == sizeof(_suite)); detail::on( events::suite{.run = +_suite}); } }; [[maybe_unused]] inline auto log = detail::log{}; [[maybe_unused]] inline auto that = detail::that_{}; [[maybe_unused]] constexpr auto test = [](const auto name) { return detail::test{"test", name}; }; [[maybe_unused]] constexpr auto should = test; [[maybe_unused]] inline auto tag = [](const auto name) { return detail::tag{{name}}; }; [[maybe_unused]] inline auto skip = tag("skip"); template [[maybe_unused]] constexpr auto type = detail::type_(); template [[nodiscard]] constexpr auto eq(const TLhs& lhs, const TRhs& rhs) { return detail::eq_{lhs, rhs}; } template [[nodiscard]] constexpr auto neq(const TLhs& lhs, const TRhs& rhs) { return detail::neq_{lhs, rhs}; } template [[nodiscard]] constexpr auto gt(const TLhs& lhs, const TRhs& rhs) { return detail::gt_{lhs, rhs}; } template [[nodiscard]] constexpr auto ge(const TLhs& lhs, const TRhs& rhs) { return detail::ge_{lhs, rhs}; } template [[nodiscard]] constexpr auto lt(const TLhs& lhs, const TRhs& rhs) { return detail::lt_{lhs, rhs}; } template [[nodiscard]] constexpr auto le(const TLhs& lhs, const TRhs& rhs) { return detail::le_{lhs, rhs}; } template [[nodiscard]] constexpr auto mut(const T& t) noexcept -> T& { return const_cast(t); } namespace bdd { [[maybe_unused]] constexpr auto feature = [](const auto name) { return detail::test{"feature", name}; }; [[maybe_unused]] constexpr auto scenario = [](const auto name) { return detail::test{"scenario", name}; }; [[maybe_unused]] constexpr auto given = [](const auto name) { return detail::test{"given", name}; }; [[maybe_unused]] constexpr auto when = [](const auto name) { return detail::test{"when", name}; }; [[maybe_unused]] constexpr auto then = [](const auto name) { return detail::test{"then", name}; }; namespace gherkin { class steps { using step_t = std::string; using steps_t = void (*)(steps&); using gherkin_t = std::vector; using call_step_t = utility::function; using call_steps_t = std::vector>; class step { public: template step(steps& steps, const TPattern& pattern) : steps_{steps}, pattern_{pattern} {} ~step() { steps_.next(pattern_); } template auto operator=(const TExpr& expr) -> void { for (const auto& [pattern, _] : steps_.call_steps()) { if (pattern_ == pattern) { return; } } steps_.call_steps().emplace_back( pattern_, [expr, pattern = pattern_](const auto& _step) { [=](type_traits::list) { log << _step; auto i = 0u; const auto& ms = utility::match(pattern, _step); expr(lexical_cast(ms[i++])...); } (typename type_traits::function_traits::args{}); }); } private: template static auto lexical_cast(const std::string& str) { T t{}; std::istringstream iss{}; iss.str(str); if constexpr (std::is_same_v) { t = iss.str(); } else { iss >> t; } return t; } steps& steps_; std::string pattern_{}; }; public: template constexpr /*explicit(false)*/ steps(const TSteps& _steps) : steps_{_steps} {} template auto operator|(const TGherkin& gherkin) { gherkin_ = utility::split(gherkin, '\n'); for (auto& _step : gherkin_) { _step.erase(0, _step.find_first_not_of(" \t")); } return [this] { step_ = {}; steps_(*this); }; } auto feature(const std::string& pattern) { return step{*this, "Feature: " + pattern}; } auto scenario(const std::string& pattern) { return step{*this, "Scenario: " + pattern}; } auto given(const std::string& pattern) { return step{*this, "Given " + pattern}; } auto when(const std::string& pattern) { return step{*this, "When " + pattern}; } auto then(const std::string& pattern) { return step{*this, "Then " + pattern}; } private: template auto next(const TPattern& pattern) -> void { const auto is_scenario = [&pattern](const auto& _step) { constexpr auto scenario = "Scenario"; return pattern.find(scenario) == std::string::npos and _step.find(scenario) != std::string::npos; }; const auto call_steps = [this, is_scenario](const auto& _step, const auto i) { for (const auto& [name, call] : call_steps_) { if (is_scenario(_step)) { break; } if (utility::is_match(_step, name) or not std::empty(utility::match(name, _step))) { step_ = i; call(_step); } } }; decltype(step_) i{}; for (const auto& _step : gherkin_) { if (i++ == step_) { call_steps(_step, i); } } } auto call_steps() -> call_steps_t& { return call_steps_; } steps_t steps_{}; gherkin_t gherkin_{}; call_steps_t call_steps_{}; decltype(sizeof("")) step_{}; }; } // namespace gherkin } // namespace bdd namespace spec { [[maybe_unused]] constexpr auto describe = [](const auto name) { return detail::test{"describe", name}; }; [[maybe_unused]] constexpr auto it = [](const auto name) { return detail::test{"it", name}; }; } // namespace spec using literals::operator""_test; using literals::operator""_b; using literals::operator""_i; using literals::operator""_s; using literals::operator""_c; using literals::operator""_sc; using literals::operator""_l; using literals::operator""_ll; using literals::operator""_u; using literals::operator""_uc; using literals::operator""_us; using literals::operator""_ul; using literals::operator""_i8; using literals::operator""_i16; using literals::operator""_i32; using literals::operator""_i64; using literals::operator""_u8; using literals::operator""_u16; using literals::operator""_u32; using literals::operator""_u64; using literals::operator""_f; using literals::operator""_d; using literals::operator""_ld; using literals::operator""_ull; using operators::operator==; using operators::operator!=; using operators::operator>; using operators::operator>=; using operators::operator<; using operators::operator<=; using operators::operator and; using operators::operator or; using operators::operator not; using operators::operator|; using operators::operator/; using operators::operator>>; } // namespace boost::inline ext::ut::inline v1_1_9 #endif