33#define TEST_SUITE(id) \
35 inline auto _hikotest_registered_##id = std::addressof(::test::register_suite<id>()); \
36 struct id : ::test::suite<id>
46#define TEST_CASE(id) \
47 void _hikotest_wrap_##id() \
51 inline static auto _hikotest_registered_##id = \
52 std::addressof(::test::register_test(&_hikotest_suite_type::_hikotest_wrap_##id, __FILE__, __LINE__, #id)); \
61#define REQUIRE(expression, ...) ::test::require(__FILE__, __LINE__, expression <=> ::test::error{__VA_ARGS__})
68#define REQUIRE_THROWS(expression, exception) \
70 bool _hikotest_throws = false; \
73 } catch (exception const&) { \
74 _hikotest_throws = true; \
76 if (not _hikotest_throws) { \
77 ::test::require(__FILE__, __LINE__, std::unexpected{std::string{#expression " did not throw " #exception "."}}); \
87#define TEST_FORCE_INLINE __forceinline
88#elif defined(__GNUC__)
89#define TEST_FORCE_INLINE __attribute__((always_inline))
91#error "TEST_FORCE_INLINE not implemented"
99#define TEST_BREAKPOINT() __debugbreak()
100#elif defined(__GNUC__)
101#define TEST_BREAKPOINT() __builtin_debugtrap()
103#error "BREAKPOINT not implemented"
109using utc_clock_type = std::chrono::utc_clock;
110using utc_time_point_type = utc_clock_type::time_point;
118inline bool break_on_failure =
false;
138 auto signature = std::string_view{__FUNCSIG__};
139#elif defined(__GNUC__)
140 auto signature = std::string_view{__PRETTY_FUNCTION__};
142#error "type_name() not implemented"
146 if (
auto first = signature.find(
"::type_name() [with T = "); first != signature.npos) {
148 auto const last = signature.
find(
"; ", first);
149 if (last == signature.npos) {
150 std::println(stderr,
"{}({}): error: Could not parse type_name from '{}'", __FILE__, __LINE__, signature);
157 if (
auto first = signature.find(
"::type_name(void) [T = "); first != signature.npos) {
159 auto const last = signature.
find(
"]", first);
160 if (last == signature.npos) {
161 std::println(stderr,
"{}({}): error: Could not parse type_name from '{}'", __FILE__, __LINE__, signature);
168 if (
auto first = signature.find(
"::type_name<"); first != signature.npos) {
170 auto const last = signature.
rfind(
">(void)");
174 std::println(stderr,
"{}({}): error: Could not parse type_name from '{}'", __FILE__, __LINE__, signature);
183template<
typename Arg>
184[[nodiscard]]
std::string operand_to_string(Arg
const& arg)
noexcept
188 if constexpr (
requires { std::formatter<Arg, char>{}; }) {
189 return std::format(
"{}", arg);
191 }
else if constexpr (
requires { buf << arg; }) {
195 }
else if constexpr (
requires {
to_string(arg); }) {
198 }
else if constexpr (
requires { arg.string(); }) {
201 }
else if constexpr (
requires { arg.str(); }) {
209 for (
auto const byte : bytes) {
211 r += std::format(
"<{:02x}", std::to_underlying(
byte));
213 r += std::format(
" {:02x}", std::to_underlying(
byte));
222enum class error_class {
243template<error_
class ErrorClass = error_
class::exact>
249 [[nodiscard]]
constexpr double operator+() const noexcept
256 [[nodiscard]]
constexpr double operator-() const noexcept
264error(
double) -> error<error_class::absolute>;
273template<error_
class ErrorClass,
typename T>
275 using value_type = T;
292 using value_type = bool;
302 operator std::expected<void, std::string>() const noexcept
328template<
typename RHS, error_
class ErrorClass>
329[[nodiscard]]
constexpr operand<ErrorClass, RHS> operator<=>(RHS
const& rhs, error<ErrorClass> e)
noexcept
334template<
typename LHS,
typename RHS>
335[[nodiscard]]
constexpr std::expected<void, std::string>
336operator==(LHS
const& lhs, operand<error_class::exact, RHS>
const& rhs)
noexcept
339 if constexpr (
requires { { lhs == rhs.v } -> std::convertible_to<bool>; }) {
344 std::format(
"Expected equality of these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
348 if (
std::equal_to<std::common_type_t<LHS, RHS>>{}(lhs, rhs.v)) {
352 std::format(
"Expected equality of these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
355 }
else if constexpr (
requires { std::ranges::equal(lhs, rhs.v); }) {
356 if (std::ranges::equal(lhs, rhs.v)) {
360 std::format(
"Expected equality of these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
364 []<
bool Flag =
false>() {
365 static_assert(Flag,
"hikotest: Unable to equality-compare two values.");
371template<
typename LHS,
typename RHS>
372[[nodiscard]]
constexpr std::expected<void, std::string>
373operator!=(LHS
const& lhs, operand<error_class::exact, RHS>
const& rhs)
noexcept
376 if constexpr (
requires { { lhs != rhs.v } -> std::convertible_to<bool>; }) {
381 std::format(
"Expected inequality between these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
389 std::format(
"Expected inequality between these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
392 }
else if constexpr (
requires { not std::ranges::equal(lhs, rhs.v); }) {
393 if (not std::ranges::equal(lhs, rhs.v)) {
397 std::format(
"Expected inequality between these values:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
401 []<
bool Flag =
false>() {
402 static_assert(Flag,
"hikotest: Unable to inequality-compare two values.");
408template<
typename LHS,
typename RHS,
typename Error>
412 } -> std::totally_ordered_with<Error>;
415template<
typename LHS,
typename RHS,
typename Error>
419template<
typename LHS,
typename RHS>
420[[nodiscard]]
constexpr std::expected<void, std::string>
423 auto const diff = lhs - rhs.v;
424 if (diff >= -rhs.e and diff <= +rhs.e) {
428 "Expected equality within {} of these values:\n {}\n {}",
430 operand_to_string(lhs),
431 operand_to_string(rhs.v))};
435template<
typename LHS,
typename RHS>
436[[nodiscard]]
constexpr std::expected<void, std::string>
440 auto lit = lhs.begin();
441 auto rit = rhs.v.begin();
443 auto const lend = lhs.end();
444 auto const rend = rhs.v.end();
446 while (lit != lend and rit != rend) {
447 auto const diff = *lit - *rit;
448 if (diff < -rhs.e or diff > +rhs.e) {
450 "Expected equality within {} of these values:\n {}\n {}",
452 operand_to_string(lhs),
453 operand_to_string(rhs.v))};
460 if (lit != lend or rit != rend) {
462 "Expected both range-values to the same size:\n {}\n {}", operand_to_string(lhs), operand_to_string(rhs.v))};
470 constexpr filter() noexcept : inclusions{test_filter_type{}}, exclusions() {}
473 constexpr filter& operator=(
filter const&)
noexcept =
default;
474 constexpr filter& operator=(
filter&&)
noexcept =
default;
482 [[nodiscard]]
bool match_suite(std::string_view
suite)
const noexcept;
483 [[nodiscard]]
bool match_test(std::string_view
suite, std::string_view test)
const noexcept;
486 struct test_filter_type {
499TEST_FORCE_INLINE
void require(
char const* file,
int line, std::expected<void, std::string> result)
504 }
else if (not break_on_failure) {
505 throw require_error(std::format(
"{}({}): error: {}", file, line, result.error()));
514 std::string_view file;
522 utc_time_point_type time_stamp;
526 bool completed =
false;
533 [[nodiscard]]
std::string suite_name()
const noexcept;
534 [[nodiscard]]
std::string test_name()
const noexcept;
535 [[nodiscard]] std::string_view file()
const noexcept;
536 [[nodiscard]]
int line()
const noexcept;
537 [[nodiscard]]
bool success()
const noexcept;
538 [[nodiscard]]
bool failure()
const noexcept;
539 [[nodiscard]]
bool skipped()
const noexcept;
540 void set_success()
noexcept;
542 void junit_xml(FILE* out)
const noexcept;
550 template<
typename Suite>
552 std::string_view file,
556 void (Suite::*test)()) noexcept :
557 file(file), line(line), suite_name(
std::move(suite_name)), test_name(
std::move(test_name)), _run_test([test]() {
558 return (Suite{}.*test)();
563 [[nodiscard]] result_type run_test_break()
const;
564 [[nodiscard]] result_type run_test_catch()
const;
565 [[nodiscard]] result_type run_test()
const;
566 [[nodiscard]] result_type layout() const noexcept;
576 using const_iterator = std::vector<test_case::result_type>::const_iterator;
579 utc_time_point_type time_stamp = {};
583 bool completed =
false;
590 [[nodiscard]]
std::string suite_name()
const noexcept;
591 [[nodiscard]]
size_t num_tests()
const noexcept;
592 [[nodiscard]]
size_t num_failures()
const noexcept;
593 [[nodiscard]]
size_t num_success()
const noexcept;
594 [[nodiscard]]
size_t num_skipped()
const noexcept;
595 [[nodiscard]]
size_t num_disabled()
const noexcept;
596 [[nodiscard]]
size_t num_errors()
const noexcept;
597 [[nodiscard]] const_iterator begin()
const noexcept;
598 [[nodiscard]] const_iterator end()
const noexcept;
600 void finish()
noexcept;
601 void junit_xml(FILE* out)
const noexcept;
610 using const_iterator = std::vector<test_suite::result_type>::const_iterator;
613 utc_time_point_type time_stamp = {};
617 bool completed =
false;
620 void finish()
noexcept;
621 [[nodiscard]]
size_t num_suites()
const noexcept;
622 [[nodiscard]]
size_t num_tests()
const noexcept;
623 [[nodiscard]]
size_t num_failures()
const noexcept;
624 [[nodiscard]]
size_t num_success()
const noexcept;
625 [[nodiscard]]
size_t num_disabled()
const noexcept;
626 [[nodiscard]]
size_t num_skipped()
const noexcept;
627 [[nodiscard]]
size_t num_errors()
const noexcept;
629 [[nodiscard]] const_iterator begin()
const noexcept;
630 [[nodiscard]] const_iterator end()
const noexcept;
632 void junit_xml(FILE* out)
const noexcept;
636 mutable size_t last_registered_suite = 0;
638 template<
typename Suite>
639 [[nodiscard]]
inline test_suite& register_suite() noexcept
641 auto name = type_name<Suite>();
644 if (name.ends_with(
"_test_suite")) {
645 name = name.substr(0, name.size() - 11);
646 }
else if (name.ends_with(
"_suite")) {
647 name = name.substr(0, name.size() - 6);
648 }
else if (name.ends_with(
"_tests")) {
649 name = name.substr(0, name.size() - 6);
650 }
else if (name.ends_with(
"_test")) {
651 name = name.substr(0, name.size() - 5);
655 if (
auto i = name.rfind(
':'); i != name.npos) {
656 name = name.substr(i + 1);
660 if (last_registered_suite < suites.
size() and suites[last_registered_suite].suite_name == name) {
661 return suites[last_registered_suite];
665 return item.suite_name < name;
670 if (it != suites.
end() and it->suite_name == name) {
674 return *suites.
emplace(it, name);
677 template<
typename Suite>
678 [[nodiscard]]
inline test_case&
679 register_test(
void (Suite::*test)(),
std::string_view file, int line,
std::string name) noexcept
681 if (name.ends_with(
"_test_case")) {
682 name = name.substr(0, name.size() - 10);
683 }
else if (name.ends_with(
"_case")) {
684 name = name.substr(0, name.size() - 5);
685 }
else if (name.ends_with(
"_test")) {
686 name = name.substr(0, name.size() - 5);
689 auto& suite = register_suite<Suite>();
690 auto& tests = suite.tests;
692 auto const it =
std::lower_bound(tests.begin(), tests.end(), name, [](
auto const& item,
auto const& name) {
693 return item.test_name < name;
696 if (it != tests.end() and it->test_name == name) {
699 "{}({}): error: Test {}.{} is already registered at {}({}).",
709 return *tests.emplace(it, file, line, suite.suite_name, name, test);
712 [[nodiscard]] result_type layout(
::test::filter const& filter)
noexcept;
713 [[nodiscard]] result_type list_tests(
::test::filter const& filter)
noexcept;
714 [[nodiscard]] result_type run_tests(
::test::filter const& filter);
717inline auto all = all_tests{};
719template<
typename Suite>
720[[nodiscard]]
inline test_suite& register_suite() noexcept
722 return all.template register_suite<Suite>();
725template<
typename Suite>
726[[nodiscard]]
inline test_case& register_test(
void (Suite::*test)(),
std::string_view file, int line,
std::string name) noexcept
728 return all.template register_test<Suite>(test, file, line,
std::move(name));
731inline all_tests::result_type list_tests(filter
const& filter)
noexcept;
732[[nodiscard]]
inline all_tests::result_type run_tests(filter
const& filter);
734template<
typename Suite>
736 using _hikotest_suite_type = Suite;
The comparison error.
Definition hikotest.hpp:244
constexpr double operator-() const noexcept
Get the error value as a negative number.
Definition hikotest.hpp:256
constexpr double operator+() const noexcept
Get the error value as a positive number.
Definition hikotest.hpp:249
Operand of a comparison, bound to an error-value.
Definition hikotest.hpp:274
Definition hikotest.hpp:468
filter(std::string_view str)
Create a filter from the string representation.
Definition hikotest.hpp:495
Definition hikotest.hpp:513
Definition hikotest.hpp:520
Definition hikotest.hpp:569
Definition hikotest.hpp:575
Definition hikotest.hpp:608
Definition hikotest.hpp:609
Definition hikotest.hpp:735
Definition hikotest.hpp:409
Definition hikotest.hpp:416