Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
test.h
1#ifndef SOURCEMETA_CORE_TEST_H_
2#define SOURCEMETA_CORE_TEST_H_
3
4#ifndef SOURCEMETA_CORE_TEST_EXPORT
5#include <sourcemeta/core/test_export.h>
6#endif
7
8#include <sourcemeta/core/numeric_util.h>
9
10#include <functional> // std::function
11#include <ostream> // std::ostream
12#include <source_location> // std::source_location
13#include <sstream> // std::ostringstream
14#include <string> // std::string
15#include <string_view> // std::string_view
16#include <type_traits> // std::is_integral_v, std::is_same_v, std::remove_cv_t, std::is_convertible_v, std::is_pointer_v, std::remove_cvref_t
17#include <utility> // std::cmp_equal, std::cmp_not_equal, std::cmp_less, std::cmp_greater, std::cmp_less_equal, std::cmp_greater_equal
18
27
28namespace sourcemeta::core {
29
30// Exporting symbols that depends on the standard C++ library is considered
31// safe.
32// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
33#if defined(_MSC_VER)
34#pragma warning(disable : 4251 4275)
35#endif
36
42struct SOURCEMETA_CORE_TEST_EXPORT TestAbortError {
44 std::string message;
45};
46
51SOURCEMETA_CORE_TEST_EXPORT
52auto test_register(std::string_view suite, std::string_view name,
53 std::string_view file, int line, std::function<void()> body)
54 -> int;
55
60SOURCEMETA_CORE_TEST_EXPORT
62 std::string_view name, std::function<void()> body,
63 std::source_location location = std::source_location::current()) -> int;
64
68[[noreturn]] SOURCEMETA_CORE_TEST_EXPORT auto
69test_report_failure(std::string_view file, int line, std::string_view message)
70 -> void;
71
76SOURCEMETA_CORE_TEST_EXPORT
77auto test_suite_from_path(std::string_view path) -> std::string;
78
83SOURCEMETA_CORE_TEST_EXPORT
84auto test_run(int argc, char **argv) -> int;
85
86template <typename Type>
88 std::is_integral_v<Type> && !std::is_same_v<std::remove_cv_t<Type>, bool> &&
89 !std::is_same_v<std::remove_cv_t<Type>, char> &&
90 !std::is_same_v<std::remove_cv_t<Type>, wchar_t> &&
91 !std::is_same_v<std::remove_cv_t<Type>, char8_t> &&
92 !std::is_same_v<std::remove_cv_t<Type>, char16_t> &&
93 !std::is_same_v<std::remove_cv_t<Type>, char32_t>;
94
95template <typename Left, typename Right>
96auto test_compare_equal(const Left &left, const Right &right) -> bool {
97 if constexpr (test_comparable_integer<Left> &&
99 return std::cmp_equal(left, right);
100 } else {
101 return left == right;
102 }
103}
104
105template <typename Left, typename Right>
106auto test_compare_not_equal(const Left &left, const Right &right) -> bool {
107 if constexpr (test_comparable_integer<Left> &&
109 return std::cmp_not_equal(left, right);
110 } else {
111 return left != right;
112 }
113}
114
115template <typename Left, typename Right>
116auto test_compare_less(const Left &left, const Right &right) -> bool {
117 if constexpr (test_comparable_integer<Left> &&
119 return std::cmp_less(left, right);
120 } else {
121 return left < right;
122 }
123}
124
125template <typename Left, typename Right>
126auto test_compare_greater(const Left &left, const Right &right) -> bool {
127 if constexpr (test_comparable_integer<Left> &&
129 return std::cmp_greater(left, right);
130 } else {
131 return left > right;
132 }
133}
134
135template <typename Left, typename Right>
136auto test_compare_less_equal(const Left &left, const Right &right) -> bool {
137 if constexpr (test_comparable_integer<Left> &&
139 return std::cmp_less_equal(left, right);
140 } else {
141 return left <= right;
142 }
143}
144
145template <typename Left, typename Right>
146auto test_compare_greater_equal(const Left &left, const Right &right) -> bool {
147 if constexpr (test_comparable_integer<Left> &&
149 return std::cmp_greater_equal(left, right);
150 } else {
151 return left >= right;
152 }
153}
154
155template <typename Type>
157 requires(std::ostream &stream, const Type &value) { stream << value; };
158
159// A pointer is not taken as text, as it may be null and as comparing two of
160// them compares addresses rather than the characters they point to
161template <typename Type>
163 test_streamable<Type> && !std::is_pointer_v<std::remove_cvref_t<Type>> &&
164 std::is_convertible_v<const Type &, std::string_view>;
165
166template <typename Type> auto test_stringify(const Type &value) -> std::string {
167 if constexpr (test_streamable<Type>) {
168 std::ostringstream stream;
169 stream << value;
170 return stream.str();
171 } else {
172 return "<unprintable>";
173 }
174}
175
180SOURCEMETA_CORE_TEST_EXPORT
181auto test_stringify_difference(std::string_view actual,
182 std::string_view expected) -> std::string;
183
184template <typename Left, typename Right>
185auto test_describe_mismatch(std::string_view expression, const Left &left,
186 const Right &right) -> std::string {
188 const auto difference{test_stringify_difference(left, right)};
189 if (!difference.empty()) {
190 return std::string{"expected "} + std::string{expression} + "\n" +
191 difference;
192 }
193 }
194
195 return std::string{"expected "} + std::string{expression} +
196 "\n actual: " + test_stringify(left) +
197 "\n expected: " + test_stringify(right);
198}
199
200inline auto test_c_string_equal(const char *const left, const char *const right)
201 -> bool {
202 // A null pointer is not a valid string to view, so compare pointers directly
203 if (left == nullptr || right == nullptr) {
204 return left == right;
205 }
206
207 return std::string_view{left} == std::string_view{right};
208}
209
210inline auto test_c_string_label(const char *const value) -> std::string_view {
211 return value == nullptr ? std::string_view{"(null)"}
212 : std::string_view{value};
213}
214
215// References, rather than values, so that the operands bind without copying and
216// without rejecting comparisons of non-copyable types. This aggregate only ever
217// lives for the duration of a single comparison call, so the usual hazards of
218// reference members do not apply here.
220template <typename Left, typename Right> struct test_operands {
221 // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members)
223 const Left &left;
225 const Right &right;
226 // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members)
227};
228
229// Collecting both operands through a single braced initializer keeps their
230// temporaries alive for the whole comparison and pins their evaluation to
231// left-to-right order. Binding each operand to its own reference inside the
232// macro guarantees neither
233template <typename Left, typename Right, typename Comparator>
234auto test_expect_comparison(std::string_view file, int line,
235 std::string_view expression,
236 const test_operands<Left, Right> &operands,
237 Comparator comparator) -> void {
238 if (!comparator(operands.left, operands.right)) {
240 file, line,
241 test_describe_mismatch(expression, operands.left, operands.right));
242 }
243}
244
245} // namespace sourcemeta::core
246
247#define SOURCEMETA_CORE_TEST_REGISTER(name) \
248 static auto sourcemeta_test_body_##name()->void; \
249 [[maybe_unused]] static const int sourcemeta_test_registration_##name = \
250 ::sourcemeta::core::test_register( \
251 ::sourcemeta::core::test_suite_from_path(__FILE__), #name, __FILE__, \
252 __LINE__, &sourcemeta_test_body_##name); \
253 static auto sourcemeta_test_body_##name()->void
254
255#define TEST(name) SOURCEMETA_CORE_TEST_REGISTER(name)
256
257// The fixture name is used as a base class and in token pasting, neither of
258// which can be parenthesized. The registration symbol is keyed on the test name
259// alone so that reusing a name within a file is a redefinition error, matching
260// the behavior of a plain test.
261// NOLINTBEGIN(bugprone-macro-parentheses)
262#define SOURCEMETA_CORE_TEST_REGISTER_FIXTURE(fixture, name) \
263 namespace { \
264 struct sourcemeta_test_fixture_##fixture##_##name final : public fixture { \
265 auto sourcemeta_test_body() -> void; \
266 }; \
267 } \
268 [[maybe_unused]] static const int sourcemeta_test_registration_##name = \
269 ::sourcemeta::core::test_register( \
270 ::sourcemeta::core::test_suite_from_path(__FILE__), #name, __FILE__, \
271 __LINE__, [] { \
272 sourcemeta_test_fixture_##fixture##_##name instance; \
273 instance.sourcemeta_test_body(); \
274 }); \
275 auto sourcemeta_test_fixture_##fixture##_##name::sourcemeta_test_body()->void
276// NOLINTEND(bugprone-macro-parentheses)
277
278#define TEST_F(fixture, name) \
279 SOURCEMETA_CORE_TEST_REGISTER_FIXTURE(fixture, name)
280
281#define SOURCEMETA_CORE_TEST_COMPARE(actual, expected, comparator, operation) \
282 ::sourcemeta::core::test_expect_comparison( \
283 __FILE__, __LINE__, #actual " " operation " " #expected, \
284 ::sourcemeta::core::test_operands{(actual), (expected)}, \
285 [](const auto &sourcemeta_test_left, \
286 const auto &sourcemeta_test_right) { \
287 return ::sourcemeta::core::comparator(sourcemeta_test_left, \
288 sourcemeta_test_right); \
289 })
290
291#define EXPECT_EQ(actual, expected) \
292 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_equal, "==")
293#define EXPECT_NE(actual, expected) \
294 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_not_equal, "!=")
295#define EXPECT_LT(actual, expected) \
296 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_less, "<")
297#define EXPECT_GT(actual, expected) \
298 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_greater, ">")
299#define EXPECT_LE(actual, expected) \
300 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_less_equal, "<=")
301#define EXPECT_GE(actual, expected) \
302 SOURCEMETA_CORE_TEST_COMPARE(actual, expected, test_compare_greater_equal, \
303 ">=")
304
305#define SOURCEMETA_CORE_TEST_COMPARE_FLOATING(actual, expected, type) \
306 do { \
307 const type sourcemeta_test_actual{static_cast<type>(actual)}; \
308 const type sourcemeta_test_expected{static_cast<type>(expected)}; \
309 if (!::sourcemeta::core::real_equal(sourcemeta_test_actual, \
310 sourcemeta_test_expected)) { \
311 ::sourcemeta::core::test_report_failure( \
312 __FILE__, __LINE__, \
313 ::sourcemeta::core::test_describe_mismatch( \
314 #actual " ~= " #expected, sourcemeta_test_actual, \
315 sourcemeta_test_expected)); \
316 } \
317 } while (false)
318
319#define EXPECT_DOUBLE_EQ(actual, expected) \
320 SOURCEMETA_CORE_TEST_COMPARE_FLOATING(actual, expected, double)
321#define EXPECT_FLOAT_EQ(actual, expected) \
322 SOURCEMETA_CORE_TEST_COMPARE_FLOATING(actual, expected, float)
323
324#define EXPECT_STREQ(actual, expected) \
325 do { \
326 const char *const sourcemeta_test_actual{(actual)}; \
327 const char *const sourcemeta_test_expected{(expected)}; \
328 if (!::sourcemeta::core::test_c_string_equal(sourcemeta_test_actual, \
329 sourcemeta_test_expected)) { \
330 ::sourcemeta::core::test_report_failure( \
331 __FILE__, __LINE__, \
332 ::sourcemeta::core::test_describe_mismatch( \
333 #actual " == " #expected, \
334 ::sourcemeta::core::test_c_string_label(sourcemeta_test_actual), \
335 ::sourcemeta::core::test_c_string_label( \
336 sourcemeta_test_expected))); \
337 } \
338 } while (false)
339
340#define EXPECT_TRUE(condition) \
341 do { \
342 if (!(condition)) { \
343 ::sourcemeta::core::test_report_failure(__FILE__, __LINE__, \
344 "expected true: " #condition); \
345 } \
346 } while (false)
347
348#define EXPECT_FALSE(condition) \
349 do { \
350 if ((condition)) { \
351 ::sourcemeta::core::test_report_failure(__FILE__, __LINE__, \
352 "expected false: " #condition); \
353 } \
354 } while (false)
355
356#define FAIL() \
357 ::sourcemeta::core::test_report_failure(__FILE__, __LINE__, \
358 "explicit failure")
359
360#endif
@ Type
The token type does not match the expected media type.
Definition jose_verify.h:202
std::string message
The message describing the failed expectation.
Definition test.h:44
SOURCEMETA_CORE_TEST_EXPORT auto test_register(std::string_view suite, std::string_view name, std::string_view file, int line, std::function< void()> body) -> int
SOURCEMETA_CORE_TEST_EXPORT auto test_run(int argc, char **argv) -> int
SOURCEMETA_CORE_TEST_EXPORT auto test_suite_from_path(std::string_view path) -> std::string
SOURCEMETA_CORE_TEST_EXPORT auto test_report_failure(std::string_view file, int line, std::string_view message) -> void
SOURCEMETA_CORE_TEST_EXPORT auto test_stringify_difference(std::string_view actual, std::string_view expected) -> std::string
A pair of operands captured by reference for one comparison.
Definition test.h:220
const Right & right
The right-hand operand.
Definition test.h:225
const Left & left
The left-hand operand.
Definition test.h:223