2392 lines
68 KiB
C++
2392 lines
68 KiB
C++
//
|
|
// 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(<iso646.h>)
|
|
#include <iso646.h> // 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 <array>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
#if __has_include(<unistd.h>) and __has_include(<sys/wait.h>)
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#if defined(__cpp_exceptions)
|
|
#include <exception>
|
|
#endif
|
|
|
|
#if defined(__cpp_lib_source_location)
|
|
#include <source_location>
|
|
#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>
|
|
class function;
|
|
template <class R, class... TArgs>
|
|
class function<R(TArgs...)> {
|
|
public:
|
|
constexpr function() = default;
|
|
template <class T>
|
|
constexpr /*explicit(false)*/ function(T data)
|
|
: invoke_{invoke_impl<T>},
|
|
destroy_{destroy_impl<T>},
|
|
data_{new T{static_cast<T&&>(data)}} {}
|
|
constexpr function(function&& other) noexcept
|
|
: invoke_{static_cast<decltype(other.invoke_)&&>(other.invoke_)},
|
|
destroy_{static_cast<decltype(other.destroy_)&&>(other.destroy_)},
|
|
data_{static_cast<decltype(other.data_)&&>(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 <class T>
|
|
[[nodiscard]] static auto invoke_impl(void* data, TArgs... args) -> R {
|
|
return (*static_cast<T*>(data))(args...);
|
|
}
|
|
|
|
template <class T>
|
|
static auto destroy_impl(void* data) -> void {
|
|
delete static_cast<T*>(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 <class TPattern, class TStr>
|
|
[[nodiscard]] constexpr auto match(const TPattern& pattern, const TStr& str)
|
|
-> std::vector<TStr> {
|
|
std::vector<TStr> 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 <class T = std::string_view, class TDelim>
|
|
[[nodiscard]] inline auto split(T input, TDelim delim) -> std::vector<T> {
|
|
std::vector<T> 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 <class T>
|
|
[[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 <class T>
|
|
[[nodiscard]] constexpr auto abs(const T t) -> T {
|
|
return t < T{} ? -t : t;
|
|
}
|
|
|
|
template <class T>
|
|
[[nodiscard]] constexpr auto min_value(const T& lhs, const T& rhs) -> const T& {
|
|
return (rhs < lhs) ? rhs : lhs;
|
|
}
|
|
|
|
template <class T, class TExp>
|
|
[[nodiscard]] constexpr auto pow(const T base, const TExp exp) -> T {
|
|
return exp ? T(base * pow(base, exp - TExp(1))) : T(1);
|
|
}
|
|
|
|
template <class T, char... Cs>
|
|
[[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 <class T, char... Cs>
|
|
[[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 <class T, char... Cs>
|
|
[[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 <class T, class TValue>
|
|
[[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 <class...>
|
|
struct list {};
|
|
|
|
template <class T, class...>
|
|
struct identity {
|
|
using type = T;
|
|
};
|
|
|
|
template <class T>
|
|
struct function_traits : function_traits<decltype(&T::operator())> {};
|
|
|
|
template <class R, class... TArgs>
|
|
struct function_traits<R (*)(TArgs...)> {
|
|
using result_type = R;
|
|
using args = list<TArgs...>;
|
|
};
|
|
|
|
template <class R, class... TArgs>
|
|
struct function_traits<R(TArgs...)> {
|
|
using result_type = R;
|
|
using args = list<TArgs...>;
|
|
};
|
|
|
|
template <class R, class T, class... TArgs>
|
|
struct function_traits<R (T::*)(TArgs...)> {
|
|
using result_type = R;
|
|
using args = list<TArgs...>;
|
|
};
|
|
|
|
template <class R, class T, class... TArgs>
|
|
struct function_traits<R (T::*)(TArgs...) const> {
|
|
using result_type = R;
|
|
using args = list<TArgs...>;
|
|
};
|
|
|
|
template <class T>
|
|
T&& declval();
|
|
template <class... Ts, class TExpr>
|
|
constexpr auto is_valid(TExpr expr)
|
|
-> decltype(expr(declval<Ts...>()), bool()) {
|
|
return true;
|
|
}
|
|
template <class...>
|
|
constexpr auto is_valid(...) -> bool {
|
|
return false;
|
|
}
|
|
|
|
template <class T>
|
|
static constexpr auto is_container_v =
|
|
is_valid<T>([](auto t) -> decltype(t.begin(), t.end(), void()) {});
|
|
|
|
template <class T>
|
|
static constexpr auto has_user_print =
|
|
is_valid<T>([](auto t) -> decltype(void(declval<std::ostringstream>() << t)) {});
|
|
|
|
template <class T>
|
|
static constexpr auto has_value_v =
|
|
is_valid<T>([](auto t) -> decltype(void(t.value)) {});
|
|
|
|
template <class T>
|
|
static constexpr auto has_epsilon_v =
|
|
is_valid<T>([](auto t) -> decltype(void(t.epsilon)) {});
|
|
|
|
template <class T>
|
|
inline constexpr auto is_floating_point_v = false;
|
|
template <>
|
|
inline constexpr auto is_floating_point_v<float> = true;
|
|
template <>
|
|
inline constexpr auto is_floating_point_v<double> = true;
|
|
template <>
|
|
inline constexpr auto is_floating_point_v<long double> = true;
|
|
|
|
#if defined(__clang__) or defined(_MSC_VER)
|
|
template <class From, class To>
|
|
static constexpr auto is_convertible_v = __is_convertible_to(From, To);
|
|
#else
|
|
template <class From, class To>
|
|
constexpr auto is_convertible(int) -> decltype(bool(To(declval<From>()))) {
|
|
return true;
|
|
}
|
|
template <class...>
|
|
constexpr auto is_convertible(...) {
|
|
return false;
|
|
}
|
|
template <class From, class To>
|
|
constexpr auto is_convertible_v = is_convertible<From, To>(0);
|
|
#endif
|
|
|
|
template <bool>
|
|
struct requires_ {};
|
|
template <>
|
|
struct requires_<true> {
|
|
using type = int;
|
|
};
|
|
|
|
template <bool Cond>
|
|
using requires_t = typename requires_<Cond>::type;
|
|
} // namespace type_traits
|
|
|
|
struct none {};
|
|
|
|
namespace events {
|
|
struct test_begin {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
reflection::source_location location{};
|
|
};
|
|
template <class Test, class TArg = none>
|
|
struct test {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
std::vector<std::string_view> tag{};
|
|
reflection::source_location location{};
|
|
TArg arg{};
|
|
Test run{};
|
|
|
|
constexpr auto operator()() { run_impl(static_cast<Test&&>(run), arg); }
|
|
constexpr auto operator()() const { run_impl(static_cast<Test&&>(run), arg); }
|
|
|
|
private:
|
|
static constexpr auto run_impl(Test test, const none&) { test(); }
|
|
|
|
template <class T>
|
|
static constexpr auto run_impl(T test, const TArg& arg)
|
|
-> decltype(test(arg), void()) {
|
|
test(arg);
|
|
}
|
|
|
|
template <class T>
|
|
static constexpr auto run_impl(T test, const TArg&)
|
|
-> decltype(test.template operator()<TArg>(), void()) {
|
|
test.template operator()<TArg>();
|
|
}
|
|
};
|
|
template <class Test, class TArg>
|
|
test(std::string_view, std::string_view, std::string_view,
|
|
reflection::source_location, TArg, Test) -> test<Test, TArg>;
|
|
template <class TSuite>
|
|
struct suite {
|
|
TSuite run{};
|
|
constexpr auto operator()() { run(); }
|
|
constexpr auto operator()() const { run(); }
|
|
};
|
|
template <class TSuite>
|
|
suite(TSuite) -> suite<TSuite>;
|
|
struct test_run {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
};
|
|
template <class TArg = none>
|
|
struct skip {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
TArg arg{};
|
|
};
|
|
template <class TArg>
|
|
skip(std::string_view, std::string_view, TArg) -> skip<TArg>;
|
|
struct test_skip {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
};
|
|
template <class TExpr>
|
|
struct assertion {
|
|
TExpr expr{};
|
|
reflection::source_location location{};
|
|
};
|
|
template <class TExpr>
|
|
assertion(TExpr, reflection::source_location) -> assertion<TExpr>;
|
|
template <class TExpr>
|
|
struct assertion_pass {
|
|
TExpr expr{};
|
|
reflection::source_location location{};
|
|
};
|
|
template <class TExpr>
|
|
assertion_pass(TExpr) -> assertion_pass<TExpr>;
|
|
template <class TExpr>
|
|
struct assertion_fail {
|
|
TExpr expr{};
|
|
reflection::source_location location{};
|
|
};
|
|
template <class TExpr>
|
|
assertion_fail(TExpr) -> assertion_fail<TExpr>;
|
|
struct test_end {
|
|
std::string_view type{};
|
|
std::string_view name{};
|
|
};
|
|
template <class TMsg>
|
|
struct log {
|
|
TMsg msg{};
|
|
};
|
|
template <class TMsg = std::string_view>
|
|
log(TMsg) -> log<TMsg>;
|
|
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 <class T>
|
|
[[nodiscard]] constexpr auto get_impl(const T& t, int) -> decltype(t.get()) {
|
|
return t.get();
|
|
}
|
|
template <class T>
|
|
[[nodiscard]] constexpr auto get_impl(const T& t, ...) -> decltype(auto) {
|
|
return t;
|
|
}
|
|
template <class T>
|
|
[[nodiscard]] constexpr auto get(const T& t) {
|
|
return get_impl(t, 0);
|
|
}
|
|
|
|
template <class T>
|
|
struct type_ : op {
|
|
template <class TOther>
|
|
[[nodiscard]] constexpr auto operator()(const TOther&) const
|
|
-> const type_<TOther> {
|
|
return {};
|
|
}
|
|
[[nodiscard]] constexpr auto operator==(type_<T>) -> bool { return true; }
|
|
template <class TOther>
|
|
[[nodiscard]] constexpr auto operator==(type_<TOther>) -> bool {
|
|
return false;
|
|
}
|
|
template <class TOther>
|
|
[[nodiscard]] constexpr auto operator==(const TOther&) -> bool {
|
|
return std::is_same_v<TOther, T>;
|
|
}
|
|
[[nodiscard]] constexpr auto operator!=(type_<T>) -> bool { return true; }
|
|
template <class TOther>
|
|
[[nodiscard]] constexpr auto operator!=(type_<TOther>) -> bool {
|
|
return true;
|
|
}
|
|
template <class TOther>
|
|
[[nodiscard]] constexpr auto operator!=(const TOther&) -> bool {
|
|
return not std::is_same_v<TOther, T>;
|
|
}
|
|
};
|
|
|
|
template <class T, class = int>
|
|
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 <class T>
|
|
struct value<T, type_traits::requires_t<type_traits::is_floating_point_v<T>>>
|
|
: 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<unsigned long long>(val))} {}
|
|
[[nodiscard]] constexpr explicit operator T() const { return value_; }
|
|
[[nodiscard]] constexpr decltype(auto) get() const { return value_; }
|
|
|
|
T value_{};
|
|
};
|
|
|
|
template <class T>
|
|
class value_location : public detail::value<T> {
|
|
public:
|
|
constexpr /*explicit(false)*/ value_location(
|
|
const T& t, const reflection::source_location& sl =
|
|
reflection::source_location::current())
|
|
: detail::value<T>{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>{t, precision} {
|
|
cfg::location = sl;
|
|
}
|
|
};
|
|
|
|
template <auto N>
|
|
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 <class T, auto N, auto D, auto Size, auto P = 1>
|
|
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<T, N, D, Size, -1>{};
|
|
}
|
|
[[nodiscard]] constexpr explicit operator value_type() const { return value; }
|
|
[[nodiscard]] constexpr auto get() const { return value; }
|
|
};
|
|
|
|
template <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
return TLhs::value == TRhs::value;
|
|
} else if constexpr (type_traits::has_epsilon_v<TLhs> and
|
|
type_traits::has_epsilon_v<TRhs>) {
|
|
return math::abs(get(lhs) - get(rhs)) <
|
|
math::min_value(TLhs::epsilon, TRhs::epsilon);
|
|
} else if constexpr (type_traits::has_epsilon_v<TLhs>) {
|
|
return math::abs(get(lhs) - get(rhs)) < TLhs::epsilon;
|
|
} else if constexpr (type_traits::has_epsilon_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
return TLhs::value != TRhs::value;
|
|
} else if constexpr (type_traits::has_epsilon_v<TLhs> and
|
|
type_traits::has_epsilon_v<TRhs>) {
|
|
return math::abs(get(lhs_) - get(rhs_)) >
|
|
math::min_value(TLhs::epsilon, TRhs::epsilon);
|
|
} else if constexpr (type_traits::has_epsilon_v<TLhs>) {
|
|
return math::abs(get(lhs_) - get(rhs_)) > TLhs::epsilon;
|
|
} else if constexpr (type_traits::has_epsilon_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
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<TLhs> and
|
|
type_traits::has_value_v<TRhs>) {
|
|
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 <class TLhs, class TRhs>
|
|
struct and_ : op {
|
|
constexpr and_(const TLhs& lhs = {}, const TRhs& rhs = {})
|
|
: lhs_{lhs},
|
|
rhs_{rhs},
|
|
value_{static_cast<bool>(lhs) and static_cast<bool>(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 <class TLhs, class TRhs>
|
|
struct or_ : op {
|
|
constexpr or_(const TLhs& lhs = {}, const TRhs& rhs = {})
|
|
: lhs_{lhs},
|
|
rhs_{rhs},
|
|
value_{static_cast<bool>(lhs) or static_cast<bool>(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 <class T>
|
|
struct not_ : op {
|
|
explicit constexpr not_(const T& t = {})
|
|
: t_{t}, value_{not static_cast<bool>(t)} {}
|
|
|
|
[[nodiscard]] constexpr operator bool() const { return value_; }
|
|
[[nodiscard]] constexpr auto value() const { return get(t_); }
|
|
|
|
const T t_{};
|
|
const bool value_{};
|
|
};
|
|
|
|
template <class>
|
|
struct fatal_;
|
|
|
|
#if defined(__cpp_exceptions)
|
|
template <class TExpr, class TException = void>
|
|
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 <class TExpr>
|
|
struct throws_<TExpr, void> : 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 <class TExpr>
|
|
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(<unistd.h>) and __has_include(<sys/wait.h>)
|
|
template <class TExpr>
|
|
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 <class T>
|
|
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 <class T>
|
|
auto& operator<<(const T& t) {
|
|
out_ << detail::get(t);
|
|
return *this;
|
|
}
|
|
|
|
template <class T,
|
|
type_traits::requires_t<not type_traits::has_user_print<T> and
|
|
type_traits::is_container_v<T>> = 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 <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::eq_<TLhs, TRhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " == " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::neq_<TLhs, TRhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " != " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::gt_<TLhs, TRhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " > " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::ge_<TLhs, TRhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " >= " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::lt_<TRhs, TLhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " < " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::le_<TRhs, TLhs>& op) {
|
|
return (*this << color(op) << op.lhs() << " <= " << op.rhs()
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::and_<TLhs, TRhs>& op) {
|
|
return (*this << '(' << op.lhs() << color(op) << " and " << colors_.none
|
|
<< op.rhs() << ')');
|
|
}
|
|
|
|
template <class TLhs, class TRhs>
|
|
auto& operator<<(const detail::or_<TLhs, TRhs>& op) {
|
|
return (*this << '(' << op.lhs() << color(op) << " or " << colors_.none
|
|
<< op.rhs() << ')');
|
|
}
|
|
|
|
template <class T>
|
|
auto& operator<<(const detail::not_<T>& op) {
|
|
return (*this << color(op) << "not " << op.value() << colors_.none);
|
|
}
|
|
|
|
template <class T>
|
|
auto& operator<<(const detail::fatal_<T>& fatal) {
|
|
return (*this << fatal.get());
|
|
}
|
|
|
|
#if defined(__cpp_exceptions)
|
|
template <class TExpr, class TException>
|
|
auto& operator<<(const detail::throws_<TExpr, TException>& op) {
|
|
return (*this << color(op) << "throws<"
|
|
<< reflection::type_name<TException>() << ">"
|
|
<< colors_.none);
|
|
}
|
|
|
|
template <class TExpr>
|
|
auto& operator<<(const detail::throws_<TExpr, void>& op) {
|
|
return (*this << color(op) << "throws" << colors_.none);
|
|
}
|
|
|
|
template <class TExpr>
|
|
auto& operator<<(const detail::nothrow_<TExpr>& op) {
|
|
return (*this << color(op) << "nothrow" << colors_.none);
|
|
}
|
|
#endif
|
|
|
|
#if __has_include(<unistd.h>) and __has_include(<sys/wait.h>)
|
|
template <class TExpr>
|
|
auto& operator<<(const detail::aborts_<TExpr>& op) {
|
|
return (*this << color(op) << "aborts" << colors_.none);
|
|
}
|
|
#endif
|
|
|
|
template <class T>
|
|
auto& operator<<(const detail::type_<T>&) {
|
|
return (*this << reflection::type_name<T>());
|
|
}
|
|
|
|
auto str() const { return out_.str(); }
|
|
const auto& colors() const { return colors_; }
|
|
|
|
private:
|
|
ut::colors colors_{};
|
|
std::ostringstream out_{};
|
|
};
|
|
|
|
template <class TPrinter = printer>
|
|
class reporter {
|
|
public:
|
|
constexpr auto operator=(TPrinter printer) {
|
|
printer_ = static_cast<TPrinter&&>(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 <class TMsg>
|
|
auto on(events::log<TMsg> 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 <class TExpr>
|
|
auto on(events::assertion_pass<TExpr>) -> void {
|
|
++asserts_.pass;
|
|
}
|
|
|
|
template <class TExpr>
|
|
auto on(events::assertion_fail<TExpr> 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<std::string_view> tag{};
|
|
ut::colors colors{};
|
|
bool dry_run{};
|
|
};
|
|
|
|
struct run_cfg {
|
|
bool report_errors{false};
|
|
};
|
|
|
|
template <class TReporter = reporter<printer>, 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 <class TPath>
|
|
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<std::string_view> 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<void>(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 <class TSuite>
|
|
auto on(events::suite<TSuite> suite) {
|
|
suites_.push_back(suite.run);
|
|
}
|
|
|
|
template <class... Ts>
|
|
auto on(events::test<Ts...> 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 <class... Ts>
|
|
auto on(events::skip<Ts...> test) {
|
|
reporter_.on(events::test_skip{.type = test.type, .name = test.name});
|
|
}
|
|
|
|
template <class TExpr>
|
|
[[nodiscard]] auto on(events::assertion<TExpr> assertion) -> bool {
|
|
if (dry_run_) {
|
|
return true;
|
|
}
|
|
|
|
if (static_cast<bool>(assertion.expr)) {
|
|
reporter_.on(events::assertion_pass<TExpr>{
|
|
.expr = assertion.expr, .location = assertion.location});
|
|
return true;
|
|
}
|
|
|
|
++fails_;
|
|
reporter_.on(events::assertion_fail<TExpr>{.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 <class TMsg>
|
|
auto on(events::log<TMsg> 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<void (*)()> suites_{};
|
|
std::size_t level_{};
|
|
bool run_{};
|
|
std::size_t fails_{};
|
|
std::array<std::string_view, MaxPathSize> path_{};
|
|
filter filter_{};
|
|
std::vector<std::string_view> tag_{};
|
|
bool dry_run_{};
|
|
};
|
|
|
|
struct override {};
|
|
|
|
template <class = override, class...>
|
|
[[maybe_unused]] inline auto cfg = runner<reporter<printer>>{};
|
|
|
|
namespace detail {
|
|
struct tag {
|
|
std::vector<std::string_view> name{};
|
|
};
|
|
|
|
template <class... Ts, class TEvent>
|
|
[[nodiscard]] constexpr decltype(auto) on(TEvent&& event) {
|
|
return ut::cfg<typename type_traits::identity<override, Ts...>::type>.on(
|
|
static_cast<TEvent&&>(event));
|
|
}
|
|
|
|
template <class Test>
|
|
struct test_location {
|
|
template <class T>
|
|
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<std::string_view> tag{};
|
|
|
|
template <class... Ts>
|
|
constexpr auto operator=(test_location<void (*)()> _test) {
|
|
on<Ts...>(events::test<void (*)()>{.type = type,
|
|
.name = name,
|
|
.tag = tag,
|
|
.location = _test.location,
|
|
.arg = none{},
|
|
.run = _test.test});
|
|
return _test.test;
|
|
}
|
|
|
|
template <class Test,
|
|
type_traits::requires_t<
|
|
not type_traits::is_convertible_v<Test, void (*)()>> = 0>
|
|
constexpr auto operator=(Test _test) ->
|
|
typename type_traits::identity<Test, decltype(_test())>::type {
|
|
on<Test>(events::test<Test>{.type = type,
|
|
.name = name,
|
|
.tag = tag,
|
|
.location = {},
|
|
.arg = none{},
|
|
.run = static_cast<Test&&>(_test)});
|
|
return _test;
|
|
}
|
|
|
|
constexpr auto operator=(void (*_test)(std::string_view)) const {
|
|
return _test(name);
|
|
}
|
|
|
|
template <class Test,
|
|
type_traits::requires_t<not type_traits::is_convertible_v<
|
|
Test, void (*)(std::string_view)>> = 0>
|
|
constexpr auto operator=(Test _test)
|
|
-> decltype(_test(type_traits::declval<std::string_view>())) {
|
|
return _test(name);
|
|
}
|
|
};
|
|
|
|
struct log {
|
|
struct next {
|
|
template <class TMsg>
|
|
auto& operator<<(const TMsg& msg) {
|
|
on<TMsg>(events::log{' '});
|
|
on<TMsg>(events::log{msg});
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
template <class TMsg>
|
|
auto operator<<(const TMsg& msg) -> next {
|
|
on<TMsg>(events::log{'\n'});
|
|
on<TMsg>(events::log{msg});
|
|
return next{};
|
|
}
|
|
};
|
|
|
|
template <class TExpr>
|
|
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<TExpr>(
|
|
events::assertion<TExpr>{.expr = expr_, .location = cfg::location}));
|
|
}
|
|
|
|
private:
|
|
const TExpr& expr_;
|
|
};
|
|
|
|
struct that_ {
|
|
template <class T>
|
|
struct expr {
|
|
using type = expr;
|
|
|
|
constexpr explicit expr(const T& t) : t_{t} {}
|
|
|
|
[[nodiscard]] constexpr auto operator!() const { return not_{*this}; }
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator==(const TRhs& rhs) const {
|
|
return eq_{t_, rhs};
|
|
}
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator!=(const TRhs& rhs) const {
|
|
return neq_{t_, rhs};
|
|
}
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator>(const TRhs& rhs) const {
|
|
return gt_{t_, rhs};
|
|
}
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator>=(const TRhs& rhs) const {
|
|
return ge_{t_, rhs};
|
|
}
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator<(const TRhs& rhs) const {
|
|
return lt_{t_, rhs};
|
|
}
|
|
|
|
template <class TRhs>
|
|
[[nodiscard]] constexpr auto operator<=(const TRhs& rhs) const {
|
|
return le_{t_, rhs};
|
|
}
|
|
|
|
[[nodiscard]] constexpr operator bool() const {
|
|
return static_cast<bool>(t_);
|
|
}
|
|
|
|
const T t_{};
|
|
};
|
|
|
|
template <class T>
|
|
[[nodiscard]] constexpr auto operator%(const T& t) const {
|
|
return expr{t};
|
|
}
|
|
};
|
|
|
|
template <class TExpr>
|
|
struct fatal_ : op {
|
|
using type = fatal_;
|
|
|
|
constexpr explicit fatal_(const TExpr& expr) : expr_{expr} {}
|
|
|
|
[[nodiscard]] constexpr operator bool() const {
|
|
if (static_cast<bool>(expr_)) {
|
|
} else {
|
|
cfg::wip = true;
|
|
void(on<TExpr>(
|
|
events::assertion<TExpr>{.expr = expr_, .location = cfg::location}));
|
|
on<TExpr>(events::fatal_assertion{});
|
|
}
|
|
return static_cast<bool>(expr_);
|
|
}
|
|
|
|
[[nodiscard]] constexpr decltype(auto) get() const { return expr_; }
|
|
|
|
TExpr expr_{};
|
|
};
|
|
|
|
template <class T>
|
|
struct expect_ {
|
|
constexpr explicit expect_(bool value) : value_{value} { cfg::wip = {}; }
|
|
|
|
template <class TMsg>
|
|
auto& operator<<(const TMsg& msg) {
|
|
if (not value_) {
|
|
on<T>(events::log{' '});
|
|
on<T>(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 <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_i() {
|
|
return detail::integral_constant<math::num<int, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_s() {
|
|
return detail::integral_constant<math::num<short, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_c() {
|
|
return detail::integral_constant<math::num<char, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_sc() {
|
|
return detail::integral_constant<math::num<signed char, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_l() {
|
|
return detail::integral_constant<math::num<long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_ll() {
|
|
return detail::integral_constant<math::num<long long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_u() {
|
|
return detail::integral_constant<math::num<unsigned, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_uc() {
|
|
return detail::integral_constant<math::num<unsigned char, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_us() {
|
|
return detail::integral_constant<math::num<unsigned short, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_ul() {
|
|
return detail::integral_constant<math::num<unsigned long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_ull() {
|
|
return detail::integral_constant<math::num<unsigned long long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_i8() {
|
|
return detail::integral_constant<math::num<std::int8_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_i16() {
|
|
return detail::integral_constant<math::num<std::int16_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_i32() {
|
|
return detail::integral_constant<math::num<std::int32_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_i64() {
|
|
return detail::integral_constant<math::num<std::int64_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_u8() {
|
|
return detail::integral_constant<math::num<std::uint8_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_u16() {
|
|
return detail::integral_constant<math::num<std::uint16_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_u32() {
|
|
return detail::integral_constant<math::num<std::uint32_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_u64() {
|
|
return detail::integral_constant<math::num<std::uint64_t, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_f() {
|
|
return detail::floating_point_constant<
|
|
float, math::num<unsigned long, Cs...>(),
|
|
math::den<unsigned long, Cs...>(),
|
|
math::den_size<unsigned long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_d() {
|
|
return detail::floating_point_constant<
|
|
double, math::num<unsigned long, Cs...>(),
|
|
math::den<unsigned long, Cs...>(),
|
|
math::den_size<unsigned long, Cs...>()>{};
|
|
}
|
|
|
|
template <char... Cs>
|
|
[[nodiscard]] constexpr auto operator""_ld() {
|
|
return detail::floating_point_constant<
|
|
long double, math::num<unsigned long long, Cs...>(),
|
|
math::den<unsigned long long, Cs...>(),
|
|
math::den_size<unsigned long long, Cs...>()>{};
|
|
}
|
|
|
|
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 <class T, type_traits::requires_t<type_traits::is_container_v<T>> = 0>
|
|
[[nodiscard]] constexpr auto operator==(T&& lhs, T&& rhs) {
|
|
return detail::eq_{static_cast<T&&>(lhs), static_cast<T&&>(rhs)};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_container_v<T>> = 0>
|
|
[[nodiscard]] constexpr auto operator!=(T&& lhs, T&& rhs) {
|
|
return detail::neq_{static_cast<T&&>(lhs), static_cast<T&&>(rhs)};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator==(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::eq_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator!=(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::neq_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator>(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::gt_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator>=(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::ge_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator<(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::lt_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator<=(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::le_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::and_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
[[nodiscard]] constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::or_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
[[nodiscard]] constexpr auto operator not(const T& t) {
|
|
return detail::not_{t};
|
|
}
|
|
|
|
template <class T>
|
|
[[nodiscard]] inline auto operator>>(
|
|
const T& t, const detail::value_location<detail::fatal>&) {
|
|
return detail::fatal_{t};
|
|
}
|
|
|
|
template <class Test>
|
|
[[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<std::string_view> 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 <class F, class T,
|
|
type_traits::requires_t<type_traits::is_container_v<T>> = 0>
|
|
[[nodiscard]] constexpr auto operator|(const F& f, const T& t) {
|
|
return [f, t](const auto name) {
|
|
for (const auto& arg : t) {
|
|
detail::on<F>(events::test<F, typename T::value_type>{.type = "test",
|
|
.name = name,
|
|
.tag = {},
|
|
.location = {},
|
|
.arg = arg,
|
|
.run = f});
|
|
}
|
|
};
|
|
}
|
|
|
|
template <
|
|
class F, template <class...> class T, class... Ts,
|
|
type_traits::requires_t<not type_traits::is_container_v<T<Ts...>>> = 0>
|
|
[[nodiscard]] constexpr auto operator|(const F& f, const T<Ts...>& t) {
|
|
return [f, t](const auto name) {
|
|
apply(
|
|
[f, name](const auto&... args) {
|
|
(detail::on<F>(events::test<F, Ts>{.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 <class T>
|
|
constexpr auto operator%(const T& t, const decltype(_t)&) {
|
|
return detail::value<T>{t};
|
|
}
|
|
|
|
template <class T>
|
|
inline auto operator>>(const T& t,
|
|
const detail::value_location<detail::fatal>&) {
|
|
using fatal_t = detail::fatal_<T>;
|
|
struct fatal_ : fatal_t, detail::log {
|
|
using type [[maybe_unused]] = fatal_t;
|
|
using fatal_t::fatal_t;
|
|
const detail::terse_<fatal_t> _{*this};
|
|
};
|
|
return fatal_{t};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator==(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using eq_t = detail::eq_<T, detail::value_location<typename T::value_type>>;
|
|
struct eq_ : eq_t, detail::log {
|
|
using type [[maybe_unused]] = eq_t;
|
|
using eq_t::eq_t;
|
|
const detail::terse_<eq_t> _{*this};
|
|
};
|
|
return eq_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator==(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using eq_t = detail::eq_<detail::value_location<typename T::value_type>, T>;
|
|
struct eq_ : eq_t, detail::log {
|
|
using type [[maybe_unused]] = eq_t;
|
|
using eq_t::eq_t;
|
|
const detail::terse_<eq_t> _{*this};
|
|
};
|
|
return eq_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator!=(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using neq_t = detail::neq_<T, detail::value_location<typename T::value_type>>;
|
|
struct neq_ : neq_t, detail::log {
|
|
using type [[maybe_unused]] = neq_t;
|
|
using neq_t::neq_t;
|
|
const detail::terse_<neq_t> _{*this};
|
|
};
|
|
return neq_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator!=(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using neq_t = detail::neq_<detail::value_location<typename T::value_type>, T>;
|
|
struct neq_ : neq_t {
|
|
using type [[maybe_unused]] = neq_t;
|
|
using neq_t::neq_t;
|
|
const detail::terse_<neq_t> _{*this};
|
|
};
|
|
return neq_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator>(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using gt_t = detail::gt_<T, detail::value_location<typename T::value_type>>;
|
|
struct gt_ : gt_t, detail::log {
|
|
using type [[maybe_unused]] = gt_t;
|
|
using gt_t::gt_t;
|
|
const detail::terse_<gt_t> _{*this};
|
|
};
|
|
return gt_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator>(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using gt_t = detail::gt_<detail::value_location<typename T::value_type>, T>;
|
|
struct gt_ : gt_t, detail::log {
|
|
using type [[maybe_unused]] = gt_t;
|
|
using gt_t::gt_t;
|
|
const detail::terse_<gt_t> _{*this};
|
|
};
|
|
return gt_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator>=(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using ge_t = detail::ge_<T, detail::value_location<typename T::value_type>>;
|
|
struct ge_ : ge_t, detail::log {
|
|
using type [[maybe_unused]] = ge_t;
|
|
using ge_t::ge_t;
|
|
const detail::terse_<ge_t> _{*this};
|
|
};
|
|
return ge_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator>=(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using ge_t = detail::ge_<detail::value_location<typename T::value_type>, T>;
|
|
struct ge_ : ge_t, detail::log {
|
|
using type [[maybe_unused]] = ge_t;
|
|
using ge_t::ge_t;
|
|
const detail::terse_<ge_t> _{*this};
|
|
};
|
|
return ge_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator<(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using lt_t = detail::lt_<T, detail::value_location<typename T::value_type>>;
|
|
struct lt_ : lt_t, detail::log {
|
|
using type [[maybe_unused]] = lt_t;
|
|
using lt_t::lt_t;
|
|
const detail::terse_<lt_t> _{*this};
|
|
};
|
|
return lt_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator<(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using lt_t = detail::lt_<detail::value_location<typename T::value_type>, T>;
|
|
struct lt_ : lt_t, detail::log {
|
|
using type [[maybe_unused]] = lt_t;
|
|
using lt_t::lt_t;
|
|
const detail::terse_<lt_t> _{*this};
|
|
};
|
|
return lt_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator<=(
|
|
const T& lhs, const detail::value_location<typename T::value_type>& rhs) {
|
|
using le_t = detail::le_<T, detail::value_location<typename T::value_type>>;
|
|
struct le_ : le_t, detail::log {
|
|
using type [[maybe_unused]] = le_t;
|
|
using le_t::le_t;
|
|
const detail::terse_<le_t> _{*this};
|
|
};
|
|
return le_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator<=(
|
|
const detail::value_location<typename T::value_type>& lhs, const T& rhs) {
|
|
using le_t = detail::le_<detail::value_location<typename T::value_type>, T>;
|
|
struct le_ : le_t {
|
|
using type [[maybe_unused]] = le_t;
|
|
using le_t::le_t;
|
|
const detail::terse_<le_t> _{*this};
|
|
};
|
|
return le_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) {
|
|
using and_t = detail::and_<typename TLhs::type, typename TRhs::type>;
|
|
struct and_ : and_t, detail::log {
|
|
using type [[maybe_unused]] = and_t;
|
|
using and_t::and_t;
|
|
const detail::terse_<and_t> _{*this};
|
|
};
|
|
return and_{lhs, rhs};
|
|
}
|
|
|
|
template <class TLhs, class TRhs,
|
|
type_traits::requires_t<type_traits::is_op_v<TLhs> or
|
|
type_traits::is_op_v<TRhs>> = 0>
|
|
constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) {
|
|
using or_t = detail::or_<typename TLhs::type, typename TRhs::type>;
|
|
struct or_ : or_t, detail::log {
|
|
using type [[maybe_unused]] = or_t;
|
|
using or_t::or_t;
|
|
const detail::terse_<or_t> _{*this};
|
|
};
|
|
return or_{lhs, rhs};
|
|
}
|
|
|
|
template <class T, type_traits::requires_t<type_traits::is_op_v<T>> = 0>
|
|
constexpr auto operator not(const T& t) {
|
|
using not_t = detail::not_<typename T::type>;
|
|
struct not_ : not_t, detail::log {
|
|
using type [[maybe_unused]] = not_t;
|
|
using not_t::not_t;
|
|
const detail::terse_<not_t> _{*this};
|
|
};
|
|
return not_{t};
|
|
}
|
|
|
|
} // namespace terse
|
|
} // namespace operators
|
|
|
|
template <class TExpr, type_traits::requires_t<
|
|
type_traits::is_op_v<TExpr> or
|
|
type_traits::is_convertible_v<TExpr, bool>> = 0>
|
|
constexpr auto expect(const TExpr& expr,
|
|
const reflection::source_location& sl =
|
|
reflection::source_location::current()) {
|
|
return detail::expect_<TExpr>{detail::on<TExpr>(
|
|
events::assertion<TExpr>{.expr = expr, .location = sl})};
|
|
}
|
|
|
|
[[maybe_unused]] constexpr auto fatal = detail::fatal{};
|
|
|
|
#if defined(__cpp_nontype_template_parameter_class)
|
|
template <auto Constant>
|
|
#else
|
|
template <bool Constant>
|
|
#endif
|
|
constexpr auto constant = Constant;
|
|
|
|
#if defined(__cpp_exceptions)
|
|
template <class TException, class TExpr>
|
|
[[nodiscard]] constexpr auto throws(const TExpr& expr) {
|
|
return detail::throws_<TExpr, TException>{expr};
|
|
}
|
|
|
|
template <class TExpr>
|
|
[[nodiscard]] constexpr auto throws(const TExpr& expr) {
|
|
return detail::throws_<TExpr>{expr};
|
|
}
|
|
|
|
template <class TExpr>
|
|
[[nodiscard]] constexpr auto nothrow(const TExpr& expr) {
|
|
return detail::nothrow_{expr};
|
|
}
|
|
#endif
|
|
|
|
#if __has_include(<unistd.h>) and __has_include(<sys/wait.h>)
|
|
template <class TExpr>
|
|
[[nodiscard]] constexpr auto aborts(const TExpr& expr) {
|
|
return detail::aborts_{expr};
|
|
}
|
|
#endif
|
|
|
|
using _b = detail::value<bool>;
|
|
using _c = detail::value<char>;
|
|
using _sc = detail::value<signed char>;
|
|
using _s = detail::value<short>;
|
|
using _i = detail::value<int>;
|
|
using _l = detail::value<long>;
|
|
using _ll = detail::value<long long>;
|
|
using _u = detail::value<unsigned>;
|
|
using _uc = detail::value<unsigned char>;
|
|
using _us = detail::value<unsigned short>;
|
|
using _ul = detail::value<unsigned long>;
|
|
using _ull = detail::value<unsigned long long>;
|
|
using _i8 = detail::value<std::int8_t>;
|
|
using _i16 = detail::value<std::int16_t>;
|
|
using _i32 = detail::value<std::int32_t>;
|
|
using _i64 = detail::value<std::int64_t>;
|
|
using _u8 = detail::value<std::uint8_t>;
|
|
using _u16 = detail::value<std::uint16_t>;
|
|
using _u32 = detail::value<std::uint32_t>;
|
|
using _u64 = detail::value<std::uint64_t>;
|
|
using _f = detail::value<float>;
|
|
using _d = detail::value<double>;
|
|
using _ld = detail::value<long double>;
|
|
|
|
template <class T>
|
|
struct _t : detail::value<T> {
|
|
constexpr explicit _t(const T& t) : detail::value<T>{t} {}
|
|
};
|
|
|
|
struct suite {
|
|
template <class TSuite>
|
|
constexpr /*explicit(false)*/ suite(TSuite _suite) {
|
|
static_assert(1 == sizeof(_suite));
|
|
detail::on<decltype(+_suite)>(
|
|
events::suite<decltype(+_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 <class T = void>
|
|
[[maybe_unused]] constexpr auto type = detail::type_<T>();
|
|
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto eq(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::eq_{lhs, rhs};
|
|
}
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto neq(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::neq_{lhs, rhs};
|
|
}
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto gt(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::gt_{lhs, rhs};
|
|
}
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto ge(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::ge_{lhs, rhs};
|
|
}
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto lt(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::lt_{lhs, rhs};
|
|
}
|
|
template <class TLhs, class TRhs>
|
|
[[nodiscard]] constexpr auto le(const TLhs& lhs, const TRhs& rhs) {
|
|
return detail::le_{lhs, rhs};
|
|
}
|
|
|
|
template <class T>
|
|
[[nodiscard]] constexpr auto mut(const T& t) noexcept -> T& {
|
|
return const_cast<T&>(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<step_t>;
|
|
using call_step_t = utility::function<void(const std::string&)>;
|
|
using call_steps_t = std::vector<std::pair<step_t, call_step_t>>;
|
|
|
|
class step {
|
|
public:
|
|
template <class TPattern>
|
|
step(steps& steps, const TPattern& pattern)
|
|
: steps_{steps}, pattern_{pattern} {}
|
|
|
|
~step() { steps_.next(pattern_); }
|
|
|
|
template <class TExpr>
|
|
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) {
|
|
[=]<class... TArgs>(type_traits::list<TArgs...>) {
|
|
log << _step;
|
|
auto i = 0u;
|
|
const auto& ms = utility::match(pattern, _step);
|
|
expr(lexical_cast<TArgs>(ms[i++])...);
|
|
}
|
|
(typename type_traits::function_traits<TExpr>::args{});
|
|
});
|
|
}
|
|
|
|
private:
|
|
template <class T>
|
|
static auto lexical_cast(const std::string& str) {
|
|
T t{};
|
|
std::istringstream iss{};
|
|
iss.str(str);
|
|
if constexpr (std::is_same_v<T, std::string>) {
|
|
t = iss.str();
|
|
} else {
|
|
iss >> t;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
steps& steps_;
|
|
std::string pattern_{};
|
|
};
|
|
|
|
public:
|
|
template <class TSteps>
|
|
constexpr /*explicit(false)*/ steps(const TSteps& _steps) : steps_{_steps} {}
|
|
|
|
template <class TGherkin>
|
|
auto operator|(const TGherkin& gherkin) {
|
|
gherkin_ = utility::split<std::string>(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 <class TPattern>
|
|
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
|