32template<mtcore::DefaultFormattable T>
61 template <
typename TT>
62 static auto test(std::stringstream &&s, TT &&t) ->
decltype(s << std::forward<TT>(t));
65 static dummy_t test(...);
67 using return_type =
decltype(test(std::declval<std::stringstream>(), std::declval<T>()));
70 static const bool value = !std::is_same<return_type, dummy_t>::value;
74template <
typename T> std::string
as_string(
const T &val) {
80#if __cpp_constexpr >= 201907L
81 return std::string{
typeid(T).name()} +
"{?}";
89template <
typename L,
typename R>
struct CmpRes {
96inline std::string
op_name(
const std::string &op) {
97 if (op ==
"eq")
return " == ";
98 if (op ==
"ne")
return " != ";
99 if (op ==
"lt")
return " < ";
100 if (op ==
"le")
return " <= ";
101 if (op ==
"gt")
return " > ";
102 if (op ==
"ge")
return " >= ";
103 if (op ==
"check")
return "";
126 void add_error(
const std::string &cond,
const std::runtime_error &err,
const std::string &file,
size_t line) {
127 errors.push_back({cond, err.what(), file, line});
130 void add_error(
const std::string &cond,
int err,
const std::string &file,
size_t line) {
131 errors.push_back({cond, std::to_string(err), file, line});
134 void add_error(
const std::string &cond,
const std::string &file,
size_t line) {
135 errors.push_back({cond,
"UNKNOWN", file, line});
138 void add_failure(
const std::string &cond,
const std::string &file,
size_t line) {
139 failures.push_back({cond, file, line});
142 void add_failure(
const std::string &cond,
const std::string &msg,
const std::string &file,
size_t line) {
143 failures.push_back({cond, file, line, msg});
146 template <
typename L,
typename R>
148 const L &left,
const R &right,
const std::string &file,
150 failures.push_back({
"CHECK_EQ(" + raw +
")", file, line,
172 std::map<std::string, std::vector<TestCase>>
cases = {};
180 auto startTime = std::chrono::system_clock::now();
181 auto test_cases =
cases[suite];
182 std::cout << suite << std::flush;
183 bool suite_nl =
false;
184 auto print_suite_nl = [&] {
191 for (
const auto &tc : test_cases) {
192 auto tcStart = std::chrono::system_clock::now();
193 bool printed =
false;
194 auto print_case = [&] {
198 std::cout <<
"\tCASE " << tc.name << std::flush;
200 if (verbose) print_case();
206 tc.case_impl(context);
207 }
catch (
const std::runtime_error &err) {
208 context.add_error(
"Uncaught Exception", err, tc.name, 0);
210 context.add_error(
"Uncaught Exception", err, tc.name, 0);
212 context.add_error(
"Uncaught Exception", tc.name, 0);
215 tc.case_impl(context);
217 if (context.errors.empty() && context.failures.empty() && verbose) {
218 std::cout <<
" [PASS]";
219 }
else if (!context.errors.empty()) {
221 std::cout <<
" [ERROR]";
223 }
else if (!context.failures.empty()) {
225 std::cout <<
" [FAILED]";
231 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - tcStart).count()
232 <<
"ms)" << std::endl;
235 for (
const auto &err : context.errors) {
236 std::cout <<
"\t\t" << err.cond <<
" ERRED WITH " << err.err <<
"\n"
237 <<
"\t\t\tWHERE: " << err.file <<
":" << err.line <<
"\n";
240 for (
const auto &fail : context.failures) {
241 std::cout <<
"\t\t" << fail.cond <<
" FAILED! " << fail.message <<
"\n"
242 <<
"\t\t\tWHERE: " << fail.file <<
":" << fail.line <<
"\n";
249 std::cout << suite <<
" ";
254 std::cout <<
"[PASSED]";
257 std::cout <<
"[FAILED]";
260 std::cout <<
"[ERRED]";
263 std::cout <<
" (" << std::chrono::duration_cast<std::chrono::milliseconds>(
264 std::chrono::system_clock::now() - startTime).count() <<
"ms)" << std::endl;
269 std::vector<std::string> suites;
270 bool verbose =
false;
271 for (
int i = 1; i < argc; ++i) {
272 if (strcmp(
"-v", argv[i]) == 0) {
275 suites.emplace_back(argv[i]);
280 if (suites.empty()) {
282 status = std::max(status,
run_suite(verbose, suite));
286 for (
const auto &s : suites) {
287 status = std::max(status,
run_suite(verbose, s));
294inline bool check(
bool l) {
return l; }
296inline bool check(
bool l,
const std::string &) {
return l; }
298inline std::string
check_msg(
bool, std::string message =
"") {
return message; }
300template <
typename L,
typename R>
301auto eq(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left == right}; }
303template <
typename L,
typename R>
304auto ne(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left != right}; }
306template <
typename L,
typename R>
307auto lt(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left < right}; }
309template <
typename L,
typename R>
310auto le(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left <= right}; }
312template <
typename L,
typename R>
313auto gt(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left > right}; }
315template <
typename L,
typename R>
316auto ge(
const L &left,
const R &right) {
return CmpRes<L, R>{left, right, left >= right}; }
319#ifdef MTTEST_DEFINE_MAIN
320#undef MTTEST_DEFINE_MAIN
323int main(
int argc,
char **argv) {
333#define TEST_IMPL_NAME1(X, Y, S, N, SEP) X##SEP##Y##SEP##S##SEP##N
334#define TEST_IMPL_NAME(X, Y, S, N) TEST_IMPL_NAME1(X, Y, S, N, _)
335#define TEST_SHORT_NAME(X, Y) TEST_IMPL_NAME1(X, Y, S, N, _)
337#define TEST_STRINGIFY(...) #__VA_ARGS__
345#define TEST_CASE(SUITE, NAME) \
346 static void TEST_IMPL_NAME(tc_impl, __LINE__, SUITE, NAME)( \
347 mttest::TestContext & mttest__context_ctx); \
348 static const auto TEST_IMPL_NAME(tc_decl, __LINE__, SUITE, NAME) = \
350 mttest::TestCase{TEST_IMPL_NAME(tc_impl, __LINE__, SUITE, NAME), \
351 TEST_STRINGIFY(SUITE), TEST_STRINGIFY(NAME)}; \
352 static void TEST_IMPL_NAME(tc_impl, __LINE__, SUITE, \
353 NAME)(mttest::TestContext & mttest__context_ctx)
355#define MT_TEST_NARGS(...) \
356 MT_TEST_NARGS_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
357#define MT_TEST_NARGS_(_11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
359#define MT_TEST_CONC(A, B) MT_TEST_CONC_(A, B)
360#define MT_TEST_CONC_(A, B) A##B
362#define MT_TEST_GET_ELEMS(N, ...) \
363 MT_TEST_CONC(MT_TEST_GET_ELEMS_, N)(__VA_ARGS__)
364#define MT_TEST_GET_ELEMS_0(_0)
365#define MT_TEST_GET_ELEMS_1(_0, _1)
366#define MT_TEST_GET_ELEMS_2(_0, _1, _2) #_1
367#define MT_TEST_GET_ELEMS_3(_0, _1, _2, _3) #_1 #_2
368#define MT_TEST_GET_ELEMS_4(_0, _1, _2, _3, _4) #_1 #_2 #_3
369#define MT_TEST_GET_ELEMS_5(_0, _1, _2, _3, _4, _5) #_1 #_2 #_3 #_4
370#define MT_TEST_GET_ELEMS_6(_0, _1, _2, _3, _4, _5, _6) #_1 #_2 #_3 #_4 #_5
371#define MT_TEST_GET_ELEMS_7(_0, _1, _2, _3, _4, _5, _6, _7) \
372 #_1 #_2 #_3 #_4 #_5 #_6
373#define MT_TEST_GET_ELEMS_8(_0, _1, _2, _3, _4, _5, _6, _7, _8) \
374 #_1 #_2 #_3 #_4 #_5 #_6 #_7
375#define MT_TEST_GET_ELEMS_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
376 #_1 #_2 #_3 #_4 #_5 #_6 #_7 #_8
377#define MT_TEST_GET_ELEMS_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
378 #_1 #_2 #_3 #_4 #_5 #_6 #_7 #_8 #_9
379#define MT_TEST_GET_ELEMS_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
380 #_1 #_2 #_3 #_4 #_5 #_6 #_7 #_8 #_9 #_10
384#define MT_TEST_GET_NOT_LAST(...) \
385 MT_TEST_GET_ELEMS(MT_TEST_NARGS(__VA_ARGS__), _, __VA_ARGS__)
387#define MT_TEST_COND(...) MT_TEST_GET_NOT_LAST(__VA_ARGS__)
394#define CHECK_MESSAGE(...) \
396 auto TEST_SHORT_NAME(mttest_res, __LINE__) = mttest::check(__VA_ARGS__); \
397 if (!TEST_SHORT_NAME(mttest_res, __LINE__)) { \
398 mttest__context_ctx.add_failure(MT_TEST_COND(__VA_ARGS__), \
399 mttest::check_msg(__VA_ARGS__), \
400 __FILE__, __LINE__); \
402 } catch (const std::runtime_error &err) { \
403 mttest__context_ctx.add_error(MT_TEST_COND(__VA_ARGS__), err, __FILE__, \
405 } catch (int err) { \
406 mttest__context_ctx.add_error(MT_TEST_COND(__VA_ARGS__), err, __FILE__, \
409 mttest__context_ctx.add_error(MT_TEST_COND(__VA_ARGS__), __FILE__, \
419 auto TEST_SHORT_NAME(mttest_res, __LINE__) = __VA_ARGS__; \
420 if (!TEST_SHORT_NAME(mttest_res, __LINE__)) { \
421 mttest__context_ctx.add_failure(TEST_STRINGIFY(__VA_ARGS__), __FILE__, \
424 } catch (const std::runtime_error &err) { \
425 mttest__context_ctx.add_error(#__VA_ARGS__, err, __FILE__, __LINE__); \
426 } catch (int err) { \
427 mttest__context_ctx.add_error(#__VA_ARGS__, err, __FILE__, __LINE__); \
429 mttest__context_ctx.add_error(#__VA_ARGS__, __FILE__, __LINE__); \
432#define MTTEST_CHECK_CMP(COMP, ...) \
434 auto TEST_SHORT_NAME(mttest_res, __LINE__) = mttest::COMP(__VA_ARGS__); \
435 if (!TEST_SHORT_NAME(mttest_res, __LINE__).res) { \
436 mttest__context_ctx.add_failure( \
437 #COMP, #__VA_ARGS__, TEST_SHORT_NAME(mttest_res, __LINE__).left, \
438 TEST_SHORT_NAME(mttest_res, __LINE__).right, __FILE__, __LINE__); \
440 } catch (const std::runtime_error &err) { \
441 mttest__context_ctx.add_error(#__VA_ARGS__, err, __FILE__, __LINE__); \
442 } catch (int err) { \
443 mttest__context_ctx.add_error(#__VA_ARGS__, err, __FILE__, __LINE__); \
445 mttest__context_ctx.add_error(#__VA_ARGS__, __FILE__, __LINE__); \
452#define CHECK_EQ_APPROX(LEFT, RIGHT, EPSILON) \
454 auto TEST_IMPL_NAME(mttest_check_res_left, __LINE__, _L_, _O_) = LEFT; \
455 auto TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__, _L_, _M_) = \
456 TEST_IMPL_NAME(mttest_check_res_left, __LINE__, _L_, _O_) - EPSILON; \
457 auto TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__, _L_, _P_) = \
458 TEST_IMPL_NAME(mttest_check_res_left, __LINE__, _L_, _O_) + EPSILON; \
459 auto TEST_IMPL_NAME(mttest_check_res_right, __LINE__, _R_, _O_) = RIGHT; \
460 if (TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__, _L_, _M_) > \
461 TEST_IMPL_NAME(mttest_check_res_right, __LINE__, _R_, _O_) || \
462 TEST_IMPL_NAME(mttest_check_res_right, __LINE__, _R_, _O_) > \
463 TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__, _L_, _P_)) { \
464 mttest__context_ctx.add_failure( \
465 TEST_STRINGIFY((LEFT - EPSILON) <= (RIGHT) <= (LEFT + EPSILON)), \
466 std::string{"Got "} + \
468 TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__, _L_, _M_)) + \
471 TEST_IMPL_NAME(mttest_check_res_right, __LINE__, _R_, _O_)) + " <= " + \
473 TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__, _L_, _P_)), \
474 __FILE__, __LINE__); \
476 } catch (const std::runtime_error &err) { \
477 mttest__context_ctx.add_error( \
478 TEST_STRINGIFY((LEFT - EPSILON) <= (RIGHT) <= (LEFT + EPSILON)), err, \
479 __FILE__, __LINE__); \
480 } catch (int err) { \
481 mttest__context_ctx.add_error( \
482 TEST_STRINGIFY((LEFT - EPSILON) <= (RIGHT) <= (LEFT + EPSILON)), err, \
483 __FILE__, __LINE__); \
485 mttest__context_ctx.add_error( \
486 TEST_STRINGIFY((LEFT - EPSILON) <= (RIGHT) <= (LEFT + EPSILON)), \
487 __FILE__, __LINE__); \
493#define CHECK_MESSAGE(...) \
495 auto TEST_SHORT_NAME(mttest_res, __LINE__) = mttest::check(__VA_ARGS__); \
496 if (!TEST_SHORT_NAME(mttest_res, __LINE__)) { \
497 mttest__context_ctx.add_failure(MT_TEST_COND(__VA_ARGS__), \
498 mttest::check_msg(__VA_ARGS__), \
499 __FILE__, __LINE__); \
508 auto TEST_SHORT_NAME(mttest_res, __LINE__) = __VA_ARGS__; \
509 if (!TEST_SHORT_NAME(mttest_res, __LINE__)) { \
510 mttest__context_ctx.add_failure(TEST_STRINGIFY(__VA_ARGS__), __FILE__, \
515#define MTTEST_CHECK_CMP(COMP, ...) \
517 auto TEST_SHORT_NAME(mttest_res, __LINE__) = mttest::COMP(__VA_ARGS__); \
518 if (!TEST_SHORT_NAME(mttest_res, __LINE__).res) { \
519 mttest__context_ctx.add_failure( \
520 #COMP, #__VA_ARGS__, TEST_SHORT_NAME(mttest_res, __LINE__).left, \
521 TEST_SHORT_NAME(mttest_res, __LINE__).right, __FILE__, __LINE__); \
528#define CHECK_EQ_APPROX(LEFT, RIGHT, EPSILON) \
530 auto TEST_IMPL_NAME(mttest_check_res_left, __LINE__) = LEFT; \
531 auto TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__) = \
532 TEST_IMPL_NAME(mttest_check_res_left, __LINE__) - EPSILON; \
533 auto TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__) = \
534 TEST_IMPL_NAME(mttest_check_res_left, __LINE__) + EPSILON; \
535 auto TEST_IMPL_NAME(mttest_check_res_right, __LINE__) = RIGHT; \
536 if (TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__) > \
537 TEST_IMPL_NAME(mttest_check_res_right, __LINE__) || \
538 TEST_IMPL_NAME(mttest_check_res_right, __LINE__) > \
539 TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__)) { \
540 mttest__context_ctx.add_failure( \
541 TEST_STRINGIFY((LEFT - EPSILON) <= (RIGHT) <= (LEFT + EPSILON)), \
542 std::string{"Got "} + \
544 TEST_IMPL_NAME(mttest_check_res_left_minus, __LINE__)) + \
547 TEST_IMPL_NAME(mttest_check_res_right, __LINE__)) + " <= " + \
549 TEST_IMPL_NAME(mttest_check_res_left_plus, __LINE__)), \
550 __FILE__, __LINE__); \
560#define CHECK_EQ(...) MTTEST_CHECK_CMP(eq, __VA_ARGS__);
565#define CHECK_NE(...) MTTEST_CHECK_CMP(ne, __VA_ARGS__);
570#define CHECK_LT(...) MTTEST_CHECK_CMP(lt, __VA_ARGS__);
575#define CHECK_LE(...) MTTEST_CHECK_CMP(le, __VA_ARGS__);
580#define CHECK_GT(...) MTTEST_CHECK_CMP(gt, __VA_ARGS__);
585#define CHECK_GE(...) MTTEST_CHECK_CMP(ge, __VA_ARGS__);
Checks if a class can be streamd to ostream.
auto ostream_writer(std::ostream &os)
Creates a writer which passes its output to an ostream Useful for compatibility with the C++ standard...
mttest::TestSuites test_suites
std::ostream & operator<<(std::ostream &os, const T &v)
MT Unit testing framework.
auto eq(const L &left, const R &right)
Equality comparison (used by macros)
auto lt(const L &left, const R &right)
Less than comparison (used by macros)
auto ne(const L &left, const R &right)
Inequality comparison (used by macros)
std::string as_string(const T &val)
Gets a value as a string (for debug printing)
SuiteStatus
Test suite status.
auto le(const L &left, const R &right)
Less than or equal to comparison (used by macros)
bool check(bool l)
Does a check (used by macros)
std::string check_msg(bool, std::string message="")
Gets the message from a check (used by macros)
auto ge(const L &left, const R &right)
Greater than or equal to comparison (used by macros)
auto gt(const L &left, const R &right)
Greater than comparison (used by macros)
std::string op_name(const std::string &op)
Gets an operation name's output symbol.
Result of comparing left and right (retains value references for debug printing)
std::function< void(TestContext &)> case_impl
std::vector< test_fail > failures
void add_failure(const std::string &comp, const std::string &raw, const L &left, const R &right, const std::string &file, size_t line)
void add_failure(const std::string &cond, const std::string &file, size_t line)
void add_error(const std::string &cond, const std::runtime_error &err, const std::string &file, size_t line)
std::vector< test_err > errors
void add_error(const std::string &cond, int err, const std::string &file, size_t line)
void add_failure(const std::string &cond, const std::string &msg, const std::string &file, size_t line)
void add_error(const std::string &cond, const std::string &file, size_t line)
Test suite metadata and runner.
bool operator+=(const TestCase &tc)
SuiteStatus run_suite(bool verbose, const std::string &suite)
std::map< std::string, std::vector< TestCase > > cases
SuiteStatus run(int argc, char **argv)