Console Library 8.0.0
A header-only library that makes C++ simple
Loading...
Searching...
No Matches
test.h
Go to the documentation of this file.
1
9
10/*
11Copyright (c) 2026 MrXie1109
12
13Permission is hereby granted, free of charge, to any person obtaining a copy
14of this software and associated documentation files (the "Software"), to deal
15in the Software without restriction, including without limitation the rights
16to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17copies of the Software, and to permit persons to whom the Software is
18furnished to do so, subject to the following conditions:
19
20The above copyright notice and this permission notice shall be included in all
21copies or substantial portions of the Software.
22
23THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29SOFTWARE.
30*/
31
32#pragma once
33#include <cstring>
34#include <iomanip>
35#include <iostream>
36#include <string>
37
38#include "../core/csexc.h"
39#include "../io/colorful.h"
40#include "../text/fmt.h"
41#include "../text/repr.h"
42#include "./time.h"
43
44namespace console {
45#ifndef CONSOLE_NO_TEST
46
52 inline int &passed_count() {
53 static int count = 0;
54 return count;
55 }
56
62 inline int &failed_count() {
63 static int count = 0;
64 return count;
65 }
66
71 inline void pre_summary() {
72 struct Guard {
73 Guard() { std::cout << "=== Start Test ===\n" << std::endl; }
74
75 ~Guard() {
76 int passed = passed_count();
77 int failed = failed_count();
78 int total = passed + failed;
79 std::cout << "=== Test Summary ===" << std::endl;
80 std::cout << "Passed: " << passed << std::endl;
81 std::cout << "Failed: " << failed << std::endl;
82 std::cout << "Total: " << total << std::endl;
83 if (total > 0)
84 std::cout
85 << "Rate: " << std::fixed << std::setprecision(1)
86 << (static_cast<double>(passed) / total * 100) << "%"
87 << std::endl;
88 else
89 std::cout << "Rate: N/A" << std::endl;
90 }
91 };
92 static Guard guard;
93 }
94
108#define TEST(test_name) \
109 static void console_test_##test_name(); \
110 static int console_result_##test_name = [] { \
111 using namespace ::console; \
112 pre_summary(); \
113 try { \
114 console_test_##test_name(); \
115 ColorGuard cg(color::Green); \
116 std::cout << "[PASS] " #test_name "\n\n"; \
117 ++passed_count(); \
118 return 0; \
119 } catch (const ::console::AssertError &e) { \
120 ColorGuard cg(color::Red); \
121 std::cout << console::format( \
122 "[FAIL] {}\n Assert Failed ({}:{}) - {}\n\n", \
123 #test_name, \
124 __FILE__, \
125 __LINE__, \
126 e.what()); \
127 ++failed_count(); \
128 return 1; \
129 } catch (const std::system_error &e) { \
130 ColorGuard cg(color::Red); \
131 std::cout << console::format( \
132 "[FAIL] {}\n Threw Exception ({}:{})\n\twhat(): " \
133 "{}\n\tcode(): {}\n\n", \
134 #test_name, \
135 __FILE__, \
136 __LINE__, \
137 e.what(), \
138 e.code()); \
139 ++failed_count(); \
140 return 1; \
141 } catch (const std::exception &e) { \
142 ColorGuard cg(color::Red); \
143 std::cout << console::format( \
144 "[FAIL] {}\n Threw Exception ({}:{})\n\twhat(): {}\n\n", \
145 #test_name, \
146 __FILE__, \
147 __LINE__, \
148 e.what()); \
149 ++failed_count(); \
150 return 1; \
151 } catch (...) { \
152 ColorGuard cg(color::Red); \
153 std::cout << console::format( \
154 "[FAIL] {}\n Threw Exception ({}:{})\n\n", \
155 #test_name, \
156 __FILE__, \
157 __LINE__); \
158 ++failed_count(); \
159 return 1; \
160 } \
161 }(); \
162 static void console_test_##test_name()
163
164#define EXPR_WARP(expression) "`" #expression "`"
165
171#define ASSERT_TRUE(condition) \
172 do { \
173 if (!(condition)) throw console::AssertError(#condition); \
174 } while (false)
175
181#define ASSERT_FALSE(condition) \
182 do { \
183 if (condition) throw console::AssertError("!(" #condition ")"); \
184 } while (false)
185
193#define ASSERT_EQ(expected, actual) \
194 do { \
195 if ((expected) != (actual)) \
196 throw console::AssertError( \
197 console::format("{} == {}\n\texpected: {}\n\tactual: {}", \
198 EXPR_WARP(expected), \
199 EXPR_WARP(actual), \
200 expected, \
201 actual)); \
202 } while (false)
203
210#define ASSERT_NE(expected, actual) \
211 do { \
212 if ((expected) == (actual)) \
213 throw console::AssertError( \
214 console::format("{} != {}\n\texpected: {}\n\tactual: {}", \
215 EXPR_WARP(expected), \
216 EXPR_WARP(actual), \
217 expected, \
218 actual)); \
219 } while (false)
220
227#define ASSERT_LT(a, b) \
228 do { \
229 if ((a) >= (b)) \
230 throw console::AssertError( \
231 console::format("{} < {}\n\tleft: {}\n\tright: {}", \
232 EXPR_WARP(a), \
233 EXPR_WARP(b), \
234 a, \
235 b)); \
236 } while (false)
237
244#define ASSERT_GT(a, b) \
245 do { \
246 if ((a) <= (b)) \
247 throw console::AssertError( \
248 console::format("{} > {}\n\tleft: {}\n\tright: {}", \
249 EXPR_WARP(a), \
250 EXPR_WARP(b), \
251 a, \
252 b)); \
253 } while (false)
254
261#define ASSERT_LE(a, b) \
262 do { \
263 if ((a) > (b)) \
264 throw console::AssertError( \
265 console::format("{} <= {}\n\tleft: {}\n\tright: {}", \
266 EXPR_WARP(a), \
267 EXPR_WARP(b), \
268 a, \
269 b)); \
270 } while (false)
271
278#define ASSERT_GE(a, b) \
279 do { \
280 if ((a) < (b)) \
281 throw console::AssertError( \
282 console::format("{} >= {}\n\tleft: {}\n\tright: {}", \
283 EXPR_WARP(a), \
284 EXPR_WARP(b), \
285 a, \
286 b)); \
287 } while (false)
288
296#define ASSERT_NEAR(expected, actual, eps) \
297 do { \
298 auto _e = (expected); \
299 auto _a = (actual); \
300 if ((_e - _a) > (eps) || (_a - _e) > (eps)) \
301 throw console::AssertError(console::format( \
302 "{} \u2248 {} [\u00B1{}]\n\texpected: " \
303 "{:.20f}\n\tactual: {:.20f}\n\tepsilon: {:.20f}", \
304 EXPR_WARP(expected), \
305 EXPR_WARP(actual), \
306 eps, \
307 _e, \
308 _a, \
309 eps)); \
310 } while (false)
311
317#define ASSERT_NULL(ptr) \
318 do { \
319 if ((ptr) != nullptr) \
320 throw console::AssertError( \
321 console::format("{} == nullptr\n\tptr: {:#x}", \
322 EXPR_WARP(ptr), \
323 reinterpret_cast<uintptr_t>(ptr))); \
324 } while (false)
325
331#define ASSERT_NOTNULL(ptr) \
332 do { \
333 if ((ptr) == nullptr) \
334 throw console::AssertError( \
335 EXPR_WARP(ptr) " == nullptr\n\tptr: nullptr"); \
336 } while (false)
337
341 inline std::string ptr_to_str(const char *s) {
342 if (s == nullptr) return "nullptr";
343 return "\"" + std::string(s) + "\"";
344 }
345
352#define ASSERT_STREQ(expected, actual) \
353 do { \
354 const char *_e = (expected); \
355 const char *_a = (actual); \
356 if (_e == nullptr || _a == nullptr) { \
357 if (_e != _a) \
358 throw console::AssertError(console::format( \
359 "{} == {}\n\texpected: {}\n\tactual: {}", \
360 EXPR_WARP(expected), \
361 EXPR_WARP(actual), \
362 console::ptr_to_str(_e), \
363 console::ptr_to_str(_a))); \
364 } else if (std::strcmp(_e, _a) != 0) { \
365 throw console::AssertError( \
366 console::format("{} == {}\n\texpected: {}\n\tactual: {}", \
367 EXPR_WARP(expected), \
368 EXPR_WARP(actual), \
369 console::ptr_to_str(_e), \
370 console::ptr_to_str(_a))); \
371 } \
372 } while (false)
373
380#define ASSERT_STRNE(expected, actual) \
381 do { \
382 const char *_e = (expected); \
383 const char *_a = (actual); \
384 if (_e == nullptr || _a == nullptr) { \
385 if (_e != _a) \
386 throw console::AssertError(console::format( \
387 "{} != {}\n\texpected: {}\n\tactual: {}", \
388 EXPR_WARP(expected), \
389 EXPR_WARP(actual), \
390 console::ptr_to_str(_e), \
391 console::ptr_to_str(_a))); \
392 } else if (std::strcmp(_e, _a) == 0) { \
393 throw console::AssertError( \
394 console::format("{} != {}\n\texpected: {}\n\tactual: {}", \
395 EXPR_WARP(expected), \
396 EXPR_WARP(actual), \
397 console::ptr_to_str(_e), \
398 console::ptr_to_str(_a))); \
399 } \
400 } while (false)
401
408#define ASSERT_STRCASEEQ(a, b) \
409 do { \
410 const char *_a = (a); \
411 const char *_b = (b); \
412 if (_a == nullptr || _b == nullptr) { \
413 if (_a != _b) \
414 throw console::AssertError(console::format( \
415 "{} == {} [case-insensitive]\n\texpected: {}\n\t" \
416 "actual: {}", \
417 EXPR_WARP(a), \
418 EXPR_WARP(b), \
419 console::ptr_to_str(_a), \
420 console::ptr_to_str(_b))); \
421 } else { \
422 bool _eq = true; \
423 const char *_pa = _a; \
424 const char *_pb = _b; \
425 while (*_pa != '\0' && *_pb != '\0') { \
426 if (static_cast<char>(std::tolower(*_pa)) \
427 != static_cast<char>(std::tolower(*_pb))) { \
428 _eq = false; \
429 break; \
430 } \
431 ++_pa; \
432 ++_pb; \
433 } \
434 if (*_pa != *_pb) _eq = false; \
435 if (!_eq) { \
436 throw console::AssertError(console::format( \
437 "{} == {} [case-insensitive]\n\texpected: {}\n\t" \
438 "actual: {}", \
439 EXPR_WARP(a), \
440 EXPR_WARP(b), \
441 console::ptr_to_str(_a), \
442 console::ptr_to_str(_b))); \
443 } \
444 } \
445 } while (false)
446
453#define ASSERT_STRCONTAINS(str, substr) \
454 do { \
455 const char *_str = (str); \
456 const char *_substr = (substr); \
457 if (_str == nullptr || _substr == nullptr \
458 || std::strstr(_str, _substr) == nullptr) { \
459 throw console::AssertError( \
460 console::format("{} contains {}\n\tstr: {}\n\tsubstr: {}", \
461 EXPR_WARP(str), \
462 EXPR_WARP(substr), \
463 console::ptr_to_str(_str), \
464 console::ptr_to_str(_substr))); \
465 } \
466 } while (false)
467
473#define ASSERT_STRNOTCONTAINS(str, substr) \
474 do { \
475 const char *_str = (str); \
476 const char *_substr = (substr); \
477 if (_str == nullptr || _substr == nullptr \
478 || std::strstr(_str, _substr) != nullptr) { \
479 throw console::AssertError( \
480 console::format("{} does not contain {}\n\tstr: {}\n\t" \
481 "substr: {}", \
482 EXPR_WARP(str), \
483 EXPR_WARP(substr), \
484 console::ptr_to_str(_str), \
485 console::ptr_to_str(_substr))); \
486 } \
487 } while (false)
488
495#define ASSERT_THROWS(expression, exception_type) \
496 do { \
497 try { \
498 expression; \
499 throw console::AssertError( \
500 EXPR_WARP(expression) " should throw " #exception_type); \
501 } catch (const exception_type &) { \
502 } catch (...) { \
503 throw console::AssertError( \
504 EXPR_WARP(expression) " should throw " #exception_type); \
505 } \
506 } while (false)
507
513#define ASSERT_NO_THROW(expression) \
514 do { \
515 try { \
516 expression; \
517 } catch (...) { \
518 throw console::AssertError( \
519 EXPR_WARP(expression) " should not throw"); \
520 } \
521 } while (false)
522
528#define ASSERT_THROWS_ANY(expression) \
529 do { \
530 try { \
531 expression; \
532 } catch (...) { \
533 break; \
534 } \
535 throw console::AssertError(EXPR_WARP(expression) " should throw"); \
536 } while (false)
537
544#define ASSERT_CONTAINS(container, element) \
545 do { \
546 const auto &_c = (container); \
547 const auto &_e = (element); \
548 bool _found = false; \
549 for (const auto &_item : _c) \
550 if (_item == _e) { \
551 _found = true; \
552 break; \
553 } \
554 if (!_found) \
555 throw console::AssertError( \
556 EXPR_WARP(container) " contains " EXPR_WARP(element)); \
557 } while (false)
558
565#define ASSERT_SIZE_EQ(container, n) \
566 do { \
567 if ((container).size() != static_cast<std::size_t>(n)) \
568 throw console::AssertError( \
569 console::format("{}.size() == {}\n\tsize: {}\n\texpected: {}", \
570 EXPR_WARP(container), \
571 EXPR_WARP(n), \
572 (container).size(), \
573 static_cast<std::size_t>(n))); \
574 } while (false)
575
581#define ASSERT_EMPTY(container) \
582 do { \
583 if (!(container).empty()) \
584 throw console::AssertError( \
585 console::format("{} is empty\n\tsize: {}", \
586 EXPR_WARP(container), \
587 (container).size())); \
588 } while (false)
589
595#define ASSERT_NOT_EMPTY(container) \
596 do { \
597 if ((container).empty()) \
598 throw console::AssertError( \
599 EXPR_WARP(container) " is not empty\n\tsize: 0"); \
600 } while (false)
601
608#define ASSERT_TIMEOUT(expr, ms) \
609 do { \
610 auto _duration = timer([&] { expr; }); \
611 auto _limit = milliseconds(ms); \
612 if (_duration > _limit) \
613 throw console::AssertError( \
614 console::format("{} timed out after {}\n\telapsed: " \
615 "{}\n\tlimit: {}", \
616 EXPR_WARP(expr), \
617 _limit, \
618 _duration, \
619 _limit)); \
620 } while (false)
621
629#define ASSERT_PERFORMANCE(expr, ops, ms) \
630 do { \
631 auto _duration = timer([&] { \
632 for (auto i = 0ULL; i < ops; ++i) expr; \
633 }); \
634 auto _limit = milliseconds(ms); \
635 if (_duration > _limit) \
636 throw console::AssertError(console::format( \
637 "{} times {} timed out after {}\n\telapsed: {}\n\t" \
638 "limit: {}\n\tops: {}", \
639 EXPR_WARP(ops), \
640 EXPR_WARP(expr), \
641 _limit, \
642 _duration, \
643 _limit, \
644 ops)); \
645 } while (false)
646
648#define TEST_MAIN \
649 int main() {}
650
651#endif
652}
提供控制台颜色和样式修饰的 ANSI 转义序列常量。
定义 console 库使用的自定义异常类层次结构。
轻量级格式化字符串头文件库。
本库所有组件所在的顶层命名空间。
void pre_summary()
构造守卫对象,程序结束时输出摘要。
Definition test.h:71
int & failed_count()
获取失败的测试用例数量。
Definition test.h:62
int & passed_count()
获取通过的测试用例数量。
Definition test.h:52
std::string ptr_to_str(const char *s)
辅助函数:安全地将 C 字符串转换为显示字符串。
Definition test.h:341
提供类似 Python 的 repr() 函数,用于生成对象的可读字符串表示。
提供时间度量、计时、休眠和日期时间格式化功能。