🛰️航天仿真算法库 SpaceAST 0.3.1
载入中...
搜索中...
未找到
TestFramework.hpp
浏览该文件的文档.
1
30
31#pragma once
32
33// 兼容 GoogleTest 的传递包含:以前很多测试在未显式 include 的情况下使用了这些
34// 标准库头(例如 std::set<char*>)。内置框架在 gtest/gtest.h 处被包含,因此需
35// 提供等价的标准库头,以免测试因其先前依赖 gtest 的传递包含而编译失败。
36#include <string>
37#include <vector>
38#include <sstream>
39#include <functional>
40#include <utility>
41#include <memory>
42#include <map>
43#include <set>
44#include <deque>
45#include <list>
46#include <tuple>
47#include <algorithm>
48#include <type_traits>
49#include <typeinfo>
50#include <iomanip>
51#include <iosfwd>
52#include <iostream>
53#include <iterator>
54#include <streambuf>
55#include <locale>
56#include <cstdio>
57#include <cstdlib>
58#include <cstring>
59#include <cstdint>
60#include <cstddef>
61#include <cmath>
62#include <limits>
63#include <exception>
64
65// 彩色标签的平台支持:Windows(含 Win7)用经典控制台 API,POSIX 用 ANSI。
66// 用 WIN32_LEAN_AND_MEAN / NOMINMAX 尽量限制其可能污染到测试代码的宏。
67#ifdef _WIN32
68# ifndef WIN32_LEAN_AND_MEAN
69# define WIN32_LEAN_AND_MEAN
70# endif
71# ifndef NOMINMAX
72# define NOMINMAX
73# endif
74# include <windows.h>
75#else
76# include <unistd.h>
77#endif
78
79namespace ast {
80namespace testing {
81
83class Test {
84public:
85 virtual ~Test() {}
86 virtual void SetUp() {}
87 virtual void TearDown() {}
88 virtual void TestBody() = 0;
89};
90
91// 兼容 GoogleTest 的开关访问方式:testing::GTEST_FLAG(name) 展开为裸标识符
92// FLAGS_gtest_<name>(与 GoogleTest 相同的技巧),从而保留调用方书写的
93// "testing::" 前缀。采用内部链接,每个翻译单元各有一份副本;每个测试
94// 可执行文件都是单一翻译单元。
95static bool FLAGS_gtest_catch_exceptions = true;
96
97#define GTEST_FLAG(name) FLAGS_gtest_##name
98
100inline void InitGoogleTest(int* argc, char** argv) {
101 (void)argc;
102 (void)argv;
103}
104
105namespace internal {
106
109enum class TagColor { kDefault, kGreen, kRed, kYellow, kCyan, kMagenta };
110
111#ifdef _WIN32
112// 缓存控制台输出句柄与原始属性,避免每次查询。
113struct ConsoleInfo {
114 bool is_console = false;
115 HANDLE handle = INVALID_HANDLE_VALUE;
116 WORD saved = 0;
117};
118inline const ConsoleInfo& console_info() {
119 static ConsoleInfo info = []() -> ConsoleInfo {
120 ConsoleInfo ci;
121 ci.handle = GetStdHandle(STD_OUTPUT_HANDLE);
122 DWORD mode = 0;
123 CONSOLE_SCREEN_BUFFER_INFO csi;
124 if (ci.handle != INVALID_HANDLE_VALUE &&
125 GetConsoleMode(ci.handle, &mode) &&
126 GetConsoleScreenBufferInfo(ci.handle, &csi)) {
127 ci.is_console = true;
128 ci.saved = csi.wAttributes;
129 }
130 return ci;
131 }();
132 return info;
133}
134#else
136inline bool stdout_is_tty() { return ::isatty(::fileno(stdout)) != 0; }
137#endif
138
140inline void begin_color(TagColor c) {
141#ifdef _WIN32
142 const ConsoleInfo& ci = console_info();
143 if (!ci.is_console) return;
144 WORD attr = ci.saved;
145 switch (c) {
146 case TagColor::kGreen: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
147 case TagColor::kRed: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
148 case TagColor::kYellow: attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
149 case TagColor::kCyan: attr = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
150 case TagColor::kMagenta: attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
151 case TagColor::kDefault: break;
152 }
153 SetConsoleTextAttribute(ci.handle, attr);
154#else
155 if (!stdout_is_tty()) return;
156 switch (c) {
157 case TagColor::kGreen: std::fputs("\033[32m", stdout); break;
158 case TagColor::kRed: std::fputs("\033[31m", stdout); break;
159 case TagColor::kYellow: std::fputs("\033[33m", stdout); break;
160 case TagColor::kCyan: std::fputs("\033[36m", stdout); break;
161 case TagColor::kMagenta: std::fputs("\033[35m", stdout); break;
162 case TagColor::kDefault: break;
163 }
164#endif
165}
166
168inline void end_color() {
169#ifdef _WIN32
170 const ConsoleInfo& ci = console_info();
171 if (!ci.is_console) return;
172 SetConsoleTextAttribute(ci.handle, ci.saved);
173#else
174 if (!stdout_is_tty()) return;
175 std::fputs("\033[0m", stdout);
176#endif
177}
178
181inline void emit_tag(TagColor c, const char* tag) {
182 begin_color(c);
183 std::fputs(tag, stdout);
184 std::fflush(stdout);
185 end_color();
186}
187
189struct TestCase {
190 std::string suite;
191 std::string name;
192 std::function<void()> run;
193 int failures = 0;
194 bool skipped = false;
195 std::vector<std::string> trace;
196};
197
199inline std::vector<TestCase>& registry() {
200 static std::vector<TestCase> list;
201 return list;
202}
203
206 static TestCase* t = nullptr;
207 return t;
208}
209
212 TestRegistrar(const char* suite, const char* name, std::function<void()> run) {
213 TestCase t;
214 t.suite = suite;
215 t.name = name;
216 t.run = std::move(run);
217 registry().push_back(std::move(t));
218 }
219};
220
223
226
228inline void report_current_failure(const std::string& msg) {
229 if (TestCase* t = current_test()) {
230 ++t->failures;
231 for (size_t i = 0; i < t->trace.size(); ++i) {
232 std::fprintf(stderr, " %s\n", t->trace[i].c_str());
233 }
234 }
235 std::fputs(msg.c_str(), stderr);
236 std::fputc('\n', stderr);
237}
238
240inline void mark_skipped() {
241 if (TestCase* t = current_test()) {
242 t->skipped = true;
243 }
244}
245
246// 若某个类型的值可流式输出则输出之,否则为空操作。避免对未定义 operator<<
247// 的自定义类型使用 "<<" 时报编译错误。
248template <typename T>
249auto stream_value(std::ostream& os, const T& v, int) -> decltype(os << v, void()) {
250 os << v;
251}
252template <typename T>
253void stream_value(std::ostream&, const T&, long) {}
254
257public:
258 ScopedTrace(const char* file, int line, const std::string& msg) {
259 if (TestCase* t = current_test()) {
260 t->trace.push_back(std::string(file) + ":" + std::to_string(line) + ": " + msg);
261 }
262 }
263 ~ScopedTrace() {
264 if (TestCase* t = current_test()) {
265 if (!t->trace.empty()) t->trace.pop_back();
266 }
267 }
268};
269
273public:
274 template <typename T>
275 SkipHelper& operator<<(const T& v) {
276 stream_value(stream_, v, 0);
277 return *this;
278 }
279
280 ~SkipHelper() noexcept(false) {
281 (void)stream_.str(); // 读取流,避免 stream_ 因只写不读而被告警
282 mark_skipped();
283 throw SkippedTestException();
284 }
285
286private:
287 std::ostringstream stream_;
288};
289
293public:
294 FailHelper(const char* file, int line) : file_(file), line_(line) {}
295
296 template <typename T>
297 FailHelper& operator<<(const T& v) {
298 stream_value(stream_, v, 0);
299 return *this;
300 }
301
302 ~FailHelper() noexcept(false) {
303 std::ostringstream os;
304 os << file_ << ':' << line_ << ": Failure\n FAIL()";
305 const std::string extra = stream_.str();
306 if (!extra.empty()) {
307 os << "\n Extra: " << extra;
308 }
309 report_current_failure(os.str());
310 throw FatalTestException();
311 }
312
313private:
314 const char* file_;
315 int line_;
316 std::ostringstream stream_;
317};
318
323public:
324 AssertionHelper(bool ok, const char* file, int line, const char* expr, bool fatal)
325 : ok_(ok), file_(file), line_(line), expr_(expr), fatal_(fatal) {}
326
327 template <typename T>
328 AssertionHelper& operator<<(const T& v) {
329 if (!ok_) {
330 stream_value(stream_, v, 0);
331 }
332 return *this;
333 }
334
335 explicit operator bool() const { return ok_; }
336
337 // 可能抛出 FatalTestException(致命断言时),因此不声明为 noexcept。
338 ~AssertionHelper() noexcept(false) {
339 if (ok_) return;
340 std::ostringstream os;
341 os << file_ << ':' << line_ << ": Failure\n Expression: " << expr_;
342 const std::string extra = stream_.str();
343 if (!extra.empty()) {
344 os << "\n Extra: " << extra;
345 }
346 report_current_failure(os.str());
347 if (fatal_) {
348 throw FatalTestException();
349 }
350 }
351
352private:
353 bool ok_;
354 const char* file_;
355 int line_;
356 const char* expr_;
357 bool fatal_;
358 std::ostringstream stream_;
359};
360
361// 比较辅助函数。
362
363inline bool streq(const char* a, const char* b) {
364 if (!a || !b) return a == b;
365 return std::strcmp(a, b) == 0;
366}
367template <typename A, typename B>
368inline bool streq(const A& a, const B& b) {
369 return std::string(a) == std::string(b);
370}
371
372inline bool double_eq(double a, double b) {
373 if (a == b) return true;
374 if (std::isnan(a) || std::isnan(b)) return std::isnan(a) && std::isnan(b);
375 double scale = std::fabs(a);
376 if (std::fabs(b) > scale) scale = std::fabs(b);
377 if (scale < 1.0) scale = 1.0;
378 return std::fabs(a - b) <= scale * 4.0 * std::numeric_limits<double>::epsilon();
379}
380
381inline bool is_near(double a, double b, double eps) {
382 return std::fabs(a - b) <= std::fabs(eps);
383}
384
386inline int run_all_tests() {
387 std::vector<TestCase>& list = registry();
388 int total = 0, failed = 0, skipped = 0;
389
390 for (size_t i = 0; i < list.size(); ++i) {
391 TestCase& tc = list[i];
392 ++total;
393 tc.failures = 0;
394 tc.skipped = false;
395 current_test() = &tc;
396
397 emit_tag(TagColor::kGreen, "[ RUN ]");
398 std::printf(" %s.%s\n", tc.suite.c_str(), tc.name.c_str());
399 const bool caught = FLAGS_gtest_catch_exceptions;
400 try {
401 tc.run();
402 } catch (const FatalTestException&) {
403 // 致命断言(ASSERT_*)的内部控制流,始终吞掉,以便该测试按失败
404 // 计数并让运行器继续执行其余测试。
405 } catch (const SkippedTestException&) {
406 // 跳过标记已由 GTEST_SKIP 写入,直接按跳过计数。
407 } catch (const std::exception& e) {
408 if (caught) {
409 report_current_failure(std::string("stray exception: ") + e.what());
410 } else {
411 throw;
412 }
413 } catch (...) {
414 if (caught) {
415 report_current_failure("stray non-std exception");
416 } else {
417 throw;
418 }
419 }
420 current_test() = nullptr;
421
422 if (tc.skipped) {
423 ++skipped;
424 emit_tag(TagColor::kYellow, "[ SKIPPED ]");
425 std::printf(" %s.%s\n", tc.suite.c_str(), tc.name.c_str());
426 } else if (tc.failures > 0) {
427 ++failed;
428 emit_tag(TagColor::kRed, "[ FAILED ]");
429 std::printf(" %s.%s\n", tc.suite.c_str(), tc.name.c_str());
430 } else {
431 emit_tag(TagColor::kGreen, "[ OK ]");
432 std::printf(" %s.%s\n", tc.suite.c_str(), tc.name.c_str());
433 }
434 }
435
436 emit_tag(TagColor::kGreen, "\n[==========]");
437 std::printf(" %d test(s) ran.\n", total);
438 emit_tag(TagColor::kGreen, "[ PASSED ]");
439 std::printf(" %d test(s).\n", total - failed - skipped);
440 if (skipped) {
441 emit_tag(TagColor::kYellow, "[ SKIPPED ]");
442 std::printf(" %d test(s).\n", skipped);
443 }
444 if (failed) {
445 emit_tag(TagColor::kRed, "[ FAILED ]");
446 std::printf(" %d test(s).\n", failed);
447 }
448 return failed;
449}
450
451} // namespace internal
452} // namespace testing
453} // namespace ast
454
455// 兼容 GoogleTest 的词法别名:把 "testing" 映射到 ast::testing,
456// 使既有测试中 testing::Test / testing::InitGoogleTest() 等无需改动。
457namespace testing = ast::testing;
458
460inline int RUN_ALL_TESTS() { return ::testing::internal::run_all_tests(); }
461
462// -----------------------------------------------------------------------------
463// 公开宏
464// -----------------------------------------------------------------------------
465
467#define GTEST_SKIP() (::testing::internal::SkipHelper())
468
472#define AST_TRACE_CONCAT_(a, b) a##b
473#define AST_TRACE_CONCAT(a, b) AST_TRACE_CONCAT_(a, b)
474#define SCOPED_TRACE(message) \
475 ::testing::internal::ScopedTrace AST_TRACE_CONCAT(_gtest_scoped_trace_, __LINE__)(__FILE__, __LINE__, (message))
476
478#define SUCCEED() do { } while (0)
479
481#define FAIL() (::testing::internal::FailHelper(__FILE__, __LINE__))
482
483// 展开为临时 AssertionHelper,从而支持 "<<" 链式输出,并由析构函数上报结果。
484#define AST_GTEST_ASSERT_(expr, exprtext, fatal) \
485 (::testing::internal::AssertionHelper((expr), __FILE__, __LINE__, (exprtext), (fatal)))
486
487#define EXPECT_EQ(a, b) AST_GTEST_ASSERT_((a) == (b), #a " == " #b, false)
488#define ASSERT_EQ(a, b) AST_GTEST_ASSERT_((a) == (b), #a " == " #b, true)
489#define EXPECT_NE(a, b) AST_GTEST_ASSERT_((a) != (b), #a " != " #b, false)
490#define ASSERT_NE(a, b) AST_GTEST_ASSERT_((a) != (b), #a " != " #b, true)
491#define EXPECT_LT(a, b) AST_GTEST_ASSERT_((a) < (b), #a " < " #b, false)
492#define ASSERT_LT(a, b) AST_GTEST_ASSERT_((a) < (b), #a " < " #b, true)
493#define EXPECT_GT(a, b) AST_GTEST_ASSERT_((a) > (b), #a " > " #b, false)
494#define ASSERT_GT(a, b) AST_GTEST_ASSERT_((a) > (b), #a " > " #b, true)
495#define EXPECT_LE(a, b) AST_GTEST_ASSERT_((a) <= (b), #a " <= " #b, false)
496#define ASSERT_LE(a, b) AST_GTEST_ASSERT_((a) <= (b), #a " <= " #b, true)
497#define EXPECT_GE(a, b) AST_GTEST_ASSERT_((a) >= (b), #a " >= " #b, false)
498#define ASSERT_GE(a, b) AST_GTEST_ASSERT_((a) >= (b), #a " >= " #b, true)
499
500#define EXPECT_TRUE(v) AST_GTEST_ASSERT_(static_cast<bool>(v), #v, false)
501#define ASSERT_TRUE(v) AST_GTEST_ASSERT_(static_cast<bool>(v), #v, true)
502#define EXPECT_FALSE(v) AST_GTEST_ASSERT_(!static_cast<bool>(v), #v, false)
503#define ASSERT_FALSE(v) AST_GTEST_ASSERT_(!static_cast<bool>(v), #v, true)
504
505#define EXPECT_NEAR(a, b, eps) AST_GTEST_ASSERT_(::testing::internal::is_near((a), (b), (eps)), #a " near " #b, false)
506#define ASSERT_NEAR(a, b, eps) AST_GTEST_ASSERT_(::testing::internal::is_near((a), (b), (eps)), #a " near " #b, true)
507#define EXPECT_DOUBLE_EQ(a, b) AST_GTEST_ASSERT_(::testing::internal::double_eq((a), (b)), #a " == " #b, false)
508#define ASSERT_DOUBLE_EQ(a, b) AST_GTEST_ASSERT_(::testing::internal::double_eq((a), (b)), #a " == " #b, true)
509#define EXPECT_FLOAT_EQ(a, b) AST_GTEST_ASSERT_(::testing::internal::double_eq((double)(a), (double)(b)), #a " == " #b, false)
510#define ASSERT_FLOAT_EQ(a, b) AST_GTEST_ASSERT_(::testing::internal::double_eq((double)(a), (double)(b)), #a " == " #b, true)
511#define EXPECT_STREQ(a, b) AST_GTEST_ASSERT_(::testing::internal::streq((a), (b)), #a " == " #b, false)
512#define ASSERT_STREQ(a, b) AST_GTEST_ASSERT_(::testing::internal::streq((a), (b)), #a " == " #b, true)
513
514// 抛异常检查:此类检查无法使用 "<<" 流式输出,因此采用语句宏。
515#define AST_GTEST_EXPECT_THROW_(stmt, ex, fatal) \
516 do { \
517 bool ok_ = false; \
518 try { (void)(stmt); } \
519 catch (const ex&) { ok_ = true; } \
520 catch (...) {} \
521 if (!ok_) { \
522 ::testing::internal::report_current_failure( \
523 std::string("Expected exception ") + #ex + " from: " + #stmt); \
524 if (fatal) return; \
525 } \
526 } while (0)
527#define EXPECT_THROW(stmt, ex) AST_GTEST_EXPECT_THROW_(stmt, ex, false)
528#define ASSERT_THROW(stmt, ex) AST_GTEST_EXPECT_THROW_(stmt, ex, true)
529
530#define AST_GTEST_EXPECT_NO_THROW_(stmt, fatal) \
531 do { \
532 try { (void)(stmt); } \
533 catch (...) { \
534 ::testing::internal::report_current_failure( \
535 std::string("Unexpected exception from: ") + #stmt); \
536 if (fatal) return; \
537 } \
538 } while (0)
539#define EXPECT_NO_THROW(stmt) AST_GTEST_EXPECT_NO_THROW_(stmt, false)
540#define ASSERT_NO_THROW(stmt) AST_GTEST_EXPECT_NO_THROW_(stmt, true)
541
542// TEST:注册一个裸测试函数。
543#define TEST(suite, name) \
544 static void suite##_##name##_TestBody(); \
545 static ::testing::internal::TestRegistrar suite##_##name##_Reg( \
546 #suite, #name, [] { suite##_##name##_TestBody(); }); \
547 static void suite##_##name##_TestBody()
548
549// TEST_F:注册一个夹具(必须继承自 ::testing::Test)。测试类覆写 TestBody();
550// SetUp()/TearDown() 通过公开的基类接口调用,因此即使是私有覆写也能正确分发。
551#define TEST_F(fixture, name) \
552 class fixture##_##name##_Test : public fixture { \
553 public: \
554 void TestBody() override; \
555 }; \
556 static ::testing::internal::TestRegistrar fixture##_##name##_Reg( \
557 #fixture, #name, [] { \
558 std::unique_ptr<::testing::Test> obj_(new fixture##_##name##_Test()); \
559 try { \
560 obj_->SetUp(); \
561 obj_->TestBody(); \
562 } \
563 catch (const ::testing::internal::FatalTestException&) {} \
564 catch (const ::testing::internal::SkippedTestException&) {} \
565 obj_->TearDown(); \
566 }); \
567 void fixture##_##name##_Test::TestBody()
void end_color()
结束一段彩色输出,恢复默认颜色;若不支持颜色则为空操作。
定义 TestFramework.hpp:168
void emit_tag(TagColor c, const char *tag)
打印带颜色的标签(如 "[ OK ]")。写完标签后立即刷新,确保在 Windows 控制台颜色属性在下一次 SetConsoleTextAttribute 之前已生效。
定义 TestFramework.hpp:181
TestCase *& current_test()
当前正在执行的测试(用于归因失败)。
定义 TestFramework.hpp:205
void InitGoogleTest(int *argc, char **argv)
初始化(极简的)测试运行器。参数被忽略。
定义 TestFramework.hpp:100
bool stdout_is_tty()
判断标准输出是否为终端(仅 POSIX 需要)。
定义 TestFramework.hpp:136
void begin_color(TagColor c)
开启一段彩色输出;若当前输出流不支持颜色则为空操作。
定义 TestFramework.hpp:140
int RUN_ALL_TESTS()
运行全部已注册测试(返回失败数量)。
定义 TestFramework.hpp:460
void report_current_failure(const std::string &msg)
在当前测试上记录一次失败并打印消息(含 SCOPED_TRACE 栈)。
定义 TestFramework.hpp:228
int run_all_tests()
运行全部已注册测试,并返回失败数量。
定义 TestFramework.hpp:386
std::vector< TestCase > & registry()
全局测试注册表(整个程序仅一份)。
定义 TestFramework.hpp:199
void mark_skipped()
将当前测试标记为跳过。
定义 TestFramework.hpp:240
TagColor
标签颜色。Windows(含 Win7)用经典控制台 API;POSIX 用 ANSI 转义 序列,仅当输出为终端时启用。实现全部内联,不额外增加编译单元。
定义 TestFramework.hpp:109
TEST_F 夹具的基类。按需覆写 SetUp()/TearDown()。
定义 TestFramework.hpp:83
由 EXPECT_*‍/ASSERT_* 返回的临时对象;累加可选的 "<<" 消息;其析构 函数负责上报结果,并在致命断言时抛出 FatalTestException 以终止 包围的测试体。
定义 TestFramework.hpp:322
FAIL() << "msg" 的临时对象:支持 "<<" 流式输出,其析构函数上报一次 致命失败并抛出 FatalTestException 以终止测试体。
定义 TestFramework.hpp:292
SCOPED_TRACE 的 RAII 辅助类:构造时压入一条跟踪信息,析构时弹出。
定义 TestFramework.hpp:256
GTEST_SKIP() << "msg" 的临时对象:支持 "<<" 流式输出,其析构函数将 当前测试标记为跳过并抛出 SkippedTestException 以终止测试体。
定义 TestFramework.hpp:272
constexpr char kCyan[]
青色
定义 Escape.hpp:45
constexpr char kYellow[]
黄色
定义 Escape.hpp:47
Unit A
安培
定义 Unit.cpp:483
Unit T
特斯拉
定义 Unit.cpp:492
Unit B
贝尔(无量纲)
定义 Unit.cpp:506
@fixme 头文件依赖倒置
定义 AgentInit.hpp:26
由致命断言(ASSERT_*)抛出,用于终止测试体。
定义 TestFramework.hpp:222
由 GTEST_SKIP 抛出,用于跳过当前测试。
定义 TestFramework.hpp:225
一个已注册的测试用例。
定义 TestFramework.hpp:189
std::vector< std::string > trace
SCOPED_TRACE 栈
定义 TestFramework.hpp:195
在静态初始化阶段注册一个测试。
定义 TestFramework.hpp:211