summaryrefslogtreecommitdiffstats
path: root/tests/framework.hpp
blob: e3751cf12d3bdcd740b93106271486cd0a2f593e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#ifndef CEPIS_TESTS_FRAMEWORK_HPP
#define CEPIS_TESTS_FRAMEWORK_HPP

#include <cmath>
#include <functional>
#include <sstream>
#include <stdexcept>
#include <string>

namespace cepis::testing {

/// Registers a test case. `CEPIS_TEST` does this for you.
void register_test(const char* name, std::function<void()> body);

/// Runs every registered test. Returns the process exit code.
int run_all();

/// Thrown by the assertion helpers; caught by the runner.
class AssertionFailure : public std::runtime_error {
 public:
  explicit AssertionFailure(const std::string& message) : std::runtime_error(message) {}
};

struct Registrar {
  Registrar(const char* name, std::function<void()> body) { register_test(name, std::move(body)); }
};

[[noreturn]] inline void fail(const std::string& detail, const char* file, int line) {
  std::ostringstream message;
  message << file << ':' << line << ": " << detail;
  throw AssertionFailure(message.str());
}

inline void check_true(bool value, const char* text, const char* file, int line) {
  if (!value) {
    fail(std::string("expected ") + text, file, line);
  }
}

/// Comparison lives in a function rather than a macro so that the arguments are
/// bound as parameters: a `CHECK_EQ` on a member of a temporary would otherwise
/// compare against a reference that has already dangled.
template <typename Actual, typename Expected>
void check_equal(const Actual& actual, const Expected& expected, const char* text, const char* file,
                 int line) {
  if (actual == expected) {
    return;
  }
  std::ostringstream detail;
  detail << "expected " << text << ", got [" << actual << "] vs [" << expected << ']';
  fail(detail.str(), file, line);
}

inline void check_near(double actual, double expected, double tolerance, const char* text,
                       const char* file, int line) {
  if (std::fabs(actual - expected) <= tolerance) {
    return;
  }
  std::ostringstream detail;
  detail << "expected " << text << ", got " << actual << " vs " << expected;
  fail(detail.str(), file, line);
}

}  // namespace cepis::testing

#define CEPIS_TEST(test_name)                                                               \
  static void test_name();                                                                  \
  static const ::cepis::testing::Registrar registrar_##test_name(#test_name, &(test_name)); \
  static void test_name()

#define CHECK(condition) ::cepis::testing::check_true((condition), #condition, __FILE__, __LINE__)

#define CHECK_EQ(actual, expected) \
  ::cepis::testing::check_equal((actual), (expected), #actual " == " #expected, __FILE__, __LINE__)

#define CHECK_NEAR(actual, expected, tolerance)                                             \
  ::cepis::testing::check_near((actual), (expected), (tolerance), #actual " ~= " #expected, \
                               __FILE__, __LINE__)

#define CHECK_THROWS(expression, exception_type)                                             \
  do {                                                                                       \
    bool cepis_threw = false;                                                                \
    try {                                                                                    \
      (void)(expression);                                                                    \
    } catch (const exception_type&) {                                                        \
      cepis_threw = true;                                                                    \
    }                                                                                        \
    if (!cepis_threw) {                                                                      \
      ::cepis::testing::fail("expected " #expression " to throw " #exception_type, __FILE__, \
                             __LINE__);                                                      \
    }                                                                                        \
  } while (false)

#endif  // CEPIS_TESTS_FRAMEWORK_HPP