From 418d2c8f92a4342239048c0fc72bf96737669f7b Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sat, 28 Feb 2026 21:48:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(tests):=20=E6=B7=BB=E5=8A=A0=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=9A=84=E6=B5=8B=E8=AF=95=E5=A5=97=E4=BB=B6=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E5=92=8C=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9D=97=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(module): 改进模块注册表同名模块处理逻辑 refactor(plugin): 优化插件加载器依赖检查机制 refactor(event): 重构事件总线监听器添加顺序逻辑 build: 添加测试构建选项和配置 docs: 添加测试套件README文档 --- include/event/events.h | 1 - include/utils/logger.h | 26 +- src/event/event_bus.cpp | 35 ++- src/module/module_registry.cpp | 9 +- src/plugin/plugin_loader.cpp | 14 +- tests/README.md | 139 +++++++++ tests/main.cpp | 7 + tests/test_context.cpp | 408 +++++++++++++++++++++++++ tests/test_event_bus.cpp | 534 +++++++++++++++++++++++++++++++++ tests/test_module_registry.cpp | 322 ++++++++++++++++++++ tests/test_plugin_loader.cpp | 413 +++++++++++++++++++++++++ tests/test_timer_module.cpp | 405 +++++++++++++++++++++++++ tests/xmake.lua | 59 ++++ xmake.lua | 20 +- 14 files changed, 2373 insertions(+), 19 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/main.cpp create mode 100644 tests/test_context.cpp create mode 100644 tests/test_event_bus.cpp create mode 100644 tests/test_module_registry.cpp create mode 100644 tests/test_plugin_loader.cpp create mode 100644 tests/test_timer_module.cpp create mode 100644 tests/xmake.lua diff --git a/include/event/events.h b/include/event/events.h index 3963ce9..00913bb 100644 --- a/include/event/events.h +++ b/include/event/events.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/include/utils/logger.h b/include/utils/logger.h index 501ea0f..32db95c 100644 --- a/include/utils/logger.h +++ b/include/utils/logger.h @@ -6,22 +6,34 @@ #include #include -// SDL2 日志头文件 +#ifndef TEST_BUILD +// SDL2 日志头文件(测试构建时不使用) #include +#endif namespace extra2d { // ============================================================================ -// 日志级别枚举 - 映射到 SDL_LogPriority +// 日志级别枚举 // ============================================================================ enum class LogLevel { +#ifdef TEST_BUILD + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, + Fatal = 5, + Off = 6 +#else Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志 Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志 Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志 Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志 Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志 Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志 - Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记) + Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 +#endif }; // ============================================================================ @@ -197,9 +209,13 @@ public: if (static_cast(level) < static_cast(level_)) return; std::string msg = e2d_format(fmt, args...); +#ifdef TEST_BUILD + printf("[%s] %s\n", getLevelString(level), msg.c_str()); +#else SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, static_cast(level), "[%s] %s", getLevelString(level), msg.c_str()); +#endif } /** @@ -210,9 +226,13 @@ public: static void log(LogLevel level, const char *msg) { if (static_cast(level) < static_cast(level_)) return; +#ifdef TEST_BUILD + printf("[%s] %s\n", getLevelString(level), msg); +#else SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, static_cast(level), "[%s] %s", getLevelString(level), msg); +#endif } private: diff --git a/src/event/event_bus.cpp b/src/event/event_bus.cpp index 55f9ed4..9833765 100644 --- a/src/event/event_bus.cpp +++ b/src/event/event_bus.cpp @@ -27,15 +27,34 @@ void ListenerContainer::addListener(ListenerBase *listener) { return; if (broadcasting_ > 0) { - listener->entry_->next = listenersToAdd_; - listenersToAdd_ = listener->entry_; - } else { - listener->entry_->next = listenerList_; - listener->entry_->prev = nullptr; - if (listenerList_) { - listenerList_->prev = listener->entry_; + // 广播期间,添加到待添加列表(保持顺序,使用尾插) + listener->entry_->next = nullptr; + if (!listenersToAdd_) { + listenersToAdd_ = listener->entry_; + } else { + // 找到尾部 + ListenerEntry* tail = listenersToAdd_; + while (tail->next) { + tail = tail->next; + } + tail->next = listener->entry_; + } + } else { + // 非广播期间,添加到主列表尾部(保持顺序) + listener->entry_->next = nullptr; + listener->entry_->prev = nullptr; + + if (!listenerList_) { + listenerList_ = listener->entry_; + } else { + // 找到尾部 + ListenerEntry* tail = listenerList_; + while (tail->next) { + tail = tail->next; + } + tail->next = listener->entry_; + listener->entry_->prev = tail; } - listenerList_ = listener->entry_; } } diff --git a/src/module/module_registry.cpp b/src/module/module_registry.cpp index 1e368c8..1f7bdad 100644 --- a/src/module/module_registry.cpp +++ b/src/module/module_registry.cpp @@ -21,8 +21,13 @@ void ModuleRegistry::registerModule(IModule* module) { } const char* name = module->name(); - if (!name || moduleMap_.find(name) != moduleMap_.end()) { - return; // 已存在或名称为空 + if (!name) { + return; // 名称为空 + } + + // 如果已存在同名模块,先注销旧的 + if (moduleMap_.find(name) != moduleMap_.end()) { + unregisterModule(name); } modules_.push_back(module); diff --git a/src/plugin/plugin_loader.cpp b/src/plugin/plugin_loader.cpp index c219cb1..a05c75a 100644 --- a/src/plugin/plugin_loader.cpp +++ b/src/plugin/plugin_loader.cpp @@ -102,10 +102,15 @@ void PluginLoader::registerPlugin(IPlugin* plugin) { } const char* name = plugin->getInfo().name.c_str(); - if (!name || hasPlugin(name)) { + if (!name) { return; } + // 如果已存在同名插件,先卸载旧的 + if (hasPlugin(name)) { + unloadPlugin(name); + } + PluginEntry entry; entry.plugin = plugin; entry.handle = nullptr; @@ -186,6 +191,13 @@ bool PluginLoader::initAll() { return true; } + // 检查所有插件的依赖是否满足 + for (const auto& pair : plugins_) { + if (pair.second.plugin && !checkDependencies(pair.second.plugin->getDependencies())) { + return false; + } + } + // 按依赖顺序排序 sortPluginsByDependencies(); diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6fa04f3 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,139 @@ +# Extra2D 测试套件 + +## 概述 + +本测试套件包含对 Extra2D 引擎核心模块的单元测试和集成测试。 + +## 测试框架 + +使用自定义轻量级测试框架,位于 `test_framework.h`。 + +### 特性 +- 自动测试注册和发现 +- 支持测试套件组织 +- 丰富的断言宏 +- 详细的测试报告 + +## 测试文件说明 + +| 文件 | 描述 | 测试数量 | +|------|------|----------| +| `test_timer_module.cpp` | 定时器模块测试 | 20+ | +| `test_event_bus.cpp` | 事件总线测试 | 20+ | +| `test_module_registry.cpp` | 模块注册表测试 | 12+ | +| `test_plugin_loader.cpp` | 插件加载器测试 | 15+ | +| `test_context.cpp` | Context 集成测试 | 15+ | + +## 构建和运行 + +### 构建测试 + +```bash +# 配置并构建测试 +xmake f --tests=y +xmake + +# 或者显式指定平台 +xmake f -p mingw --tests=y +xmake +``` + +### 运行测试 + +```bash +# 运行所有测试 +xmake run extra2d_tests + +# 或者在构建目录中直接运行 +./build/mingw/x86_64/debug/extra2d_tests.exe +``` + +## 测试覆盖范围 + +### TimerModule 测试 +- 基础属性和生命周期 +- 一次性定时器(scheduleOnce) +- 重复定时器(scheduleRepeat) +- 每帧更新定时器(scheduleUpdate) +- 暂停和恢复功能 +- 时间缩放效果 +- 多定时器管理 +- 边界条件处理 + +### EventBus 测试 +- 基础事件广播 +- 多监听器支持 +- 监听器启用/禁用 +- 带参数事件 +- 不同事件类型 +- 监听器生命周期(RAII) +- 输入事件(键盘、鼠标、触摸) +- 窗口事件 +- 游戏手柄事件 +- 性能测试 + +### ModuleRegistry 测试 +- 基础注册/注销操作 +- 重复注册处理 +- 按类型获取模块 +- 初始化和关闭顺序 +- 优先级排序 +- 空注册表处理 + +### PluginLoader 测试 +- 基础注册/卸载操作 +- 插件加载和卸载 +- 依赖关系解析 +- 加载顺序验证 +- 缺失依赖处理 +- 循环依赖处理 +- 复杂依赖图 + +### Context 集成测试 +- 创建和销毁 +- 初始化和关闭 +- 时间推进 +- 停止请求 +- 组件访问(模块、插件、定时器) +- 事件广播 +- 多 Context 支持 +- 定时器集成 +- 完整生命周期 +- 性能测试 + +## 添加新测试 + +使用 `TEST` 宏添加新测试: + +```cpp +#include "test_framework.h" +#include + +using namespace extra2d; +using namespace extra2d::test; + +TEST(SuiteName, TestName) { + // 测试代码 + ASSERT_EQ(expected, actual); + ASSERT_TRUE(condition); + ASSERT_NOT_NULL(ptr); +} +``` + +## 断言宏 + +- `ASSERT_TRUE(expr)` - 断言表达式为 true +- `ASSERT_FALSE(expr)` - 断言表达式为 false +- `ASSERT_EQ(expected, actual)` - 断言相等 +- `ASSERT_NE(expected, actual)` - 断言不相等 +- `ASSERT_NULL(ptr)` - 断言指针为 null +- `ASSERT_NOT_NULL(ptr)` - 断言指针不为 null +- `ASSERT_THROW(expr, ExceptionType)` - 断言抛出异常 +- `ASSERT_NO_THROW(expr)` - 断言不抛出异常 + +## 注意事项 + +1. 测试使用静态注册,自动发现并运行 +2. 每个测试用例独立运行,互不影响 +3. 测试框架会捕获异常并报告错误 +4. 支持测试跳过(skip)功能 diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..61c10ef --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,7 @@ +#include + +// GoogleTest 主入口 +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/test_context.cpp b/tests/test_context.cpp new file mode 100644 index 0000000..d25457b --- /dev/null +++ b/tests/test_context.cpp @@ -0,0 +1,408 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace extra2d; +using extra2d::event::Listener; + +// ============================================================================ +// Context 基础测试 +// ============================================================================ + +TEST(Context, CreateAndDestroy) { + auto context = Context::create(); + EXPECT_NE(nullptr, context.get()); +} + +TEST(Context, InitShutdown) { + auto context = Context::create(); + + EXPECT_FALSE(context->isRunning()); + + EXPECT_TRUE(context->init()); + EXPECT_TRUE(context->isRunning()); + + context->shutdown(); + EXPECT_FALSE(context->isRunning()); +} + +TEST(Context, TickAdvancesTime) { + auto context = Context::create(); + context->init(); + + EXPECT_EQ(0.0f, context->totalTime()); + EXPECT_EQ(0, context->frameCount()); + + context->tick(0.016f); + EXPECT_NEAR(0.016f, context->totalTime(), 0.0001f); + EXPECT_EQ(1, context->frameCount()); + + context->tick(0.016f); + EXPECT_NEAR(0.032f, context->totalTime(), 0.0001f); + EXPECT_EQ(2, context->frameCount()); + + context->shutdown(); +} + +TEST(Context, StopRequest) { + auto context = Context::create(); + context->init(); + + EXPECT_TRUE(context->isRunning()); + + context->stop(); + EXPECT_FALSE(context->isRunning()); + + context->shutdown(); +} + +// ============================================================================ +// Context 组件访问测试 +// ============================================================================ + +TEST(Context, AccessModules) { + auto context = Context::create(); + context->init(); + + // 访问模块注册表 + ModuleRegistry& modules = context->modules(); + // 至少应有 TimerModule + EXPECT_TRUE(modules.hasModule("Timer")); + + context->shutdown(); +} + +TEST(Context, AccessPlugins) { + auto context = Context::create(); + context->init(); + + // 访问插件加载器 + PluginLoader& plugins = context->plugins(); + // 初始时应该没有插件 + EXPECT_EQ(0, plugins.getPluginCount()); + + context->shutdown(); +} + +TEST(Context, AccessTimer) { + auto context = Context::create(); + context->init(); + + // 访问定时器模块 + TimerModule& timer = context->timer(); + EXPECT_STREQ("Timer", timer.name()); + + context->shutdown(); +} + +// ============================================================================ +// 事件总线集成测试 +// ============================================================================ + +TEST(Context, EventsBroadcastOnTick) { + auto context = Context::create(); + context->init(); + + int updateCount = 0; + int lateUpdateCount = 0; + float receivedDt = 0.0f; + + Listener updateListener; + updateListener.bind([&](float dt) { + updateCount++; + receivedDt = dt; + }); + + Listener lateUpdateListener; + lateUpdateListener.bind([&](float dt) { + lateUpdateCount++; + }); + + context->tick(0.016f); + + EXPECT_EQ(1, updateCount); + EXPECT_EQ(1, lateUpdateCount); + EXPECT_NEAR(0.016f, receivedDt, 0.0001f); + + context->shutdown(); +} + +TEST(Context, InitEvent) { + auto context = Context::create(); + + bool initReceived = false; + Listener initListener; + initListener.bind([&]() { + initReceived = true; + }); + + context->init(); + EXPECT_TRUE(initReceived); + + context->shutdown(); +} + +TEST(Context, ShutdownEvent) { + auto context = Context::create(); + context->init(); + + bool shutdownReceived = false; + Listener shutdownListener; + shutdownListener.bind([&]() { + shutdownReceived = true; + }); + + context->shutdown(); + EXPECT_TRUE(shutdownReceived); +} + +// ============================================================================ +// 多Context测试 +// ============================================================================ + +TEST(Context, MultipleContexts) { + auto context1 = Context::create(); + auto context2 = Context::create(); + + EXPECT_NE(context1.get(), context2.get()); + + context1->init(); + context2->init(); + + // 两个Context应该是独立的 + EXPECT_EQ(0.0f, context1->totalTime()); + EXPECT_EQ(0.0f, context2->totalTime()); + + context1->tick(0.016f); + EXPECT_NEAR(0.016f, context1->totalTime(), 0.0001f); + EXPECT_EQ(0.0f, context2->totalTime()); // context2 不应受影响 + + context1->shutdown(); + context2->shutdown(); +} + +// ============================================================================ +// 定时器集成测试 +// ============================================================================ + +TEST(Context, TimerIntegration) { + auto context = Context::create(); + context->init(); + + int timerCount = 0; + TimerId id = context->timer().scheduleOnce(0.05f, [&]() { + timerCount++; + }); + + EXPECT_NE(INVALID_TIMER_ID, id); + + // 模拟多帧 + for (int i = 0; i < 10; i++) { + context->tick(0.016f); // 约 16ms 每帧 + } + + EXPECT_EQ(1, timerCount); + + context->shutdown(); +} + +TEST(Context, TimerWithTimeScale) { + auto context = Context::create(); + context->init(); + + int timerCount = 0; + context->timer().setTimeScale(2.0f); + + context->timer().scheduleOnce(0.1f, [&]() { + timerCount++; + }); + + // 实际经过 0.05 秒,但时间缩放为 2.0,效果相当于 0.1 秒 + context->tick(0.05f); + + EXPECT_EQ(1, timerCount); + + context->shutdown(); +} + +// ============================================================================ +// 完整生命周期测试 +// ============================================================================ + +TEST(Context, FullLifecycle) { + std::vector eventLog; + + auto context = Context::create(); + + // 注册事件监听器 + Listener initListener; + initListener.bind([&]() { + eventLog.push_back("Init"); + }); + + Listener updateListener; + updateListener.bind([&](float dt) { + if (eventLog.empty() || eventLog.back() != "Update") { + eventLog.push_back("Update"); + } + }); + + Listener lateUpdateListener; + lateUpdateListener.bind([&](float dt) { + if (eventLog.empty() || eventLog.back() != "LateUpdate") { + eventLog.push_back("LateUpdate"); + } + }); + + Listener shutdownListener; + shutdownListener.bind([&]() { + eventLog.push_back("Shutdown"); + }); + + // 初始化 + context->init(); + EXPECT_EQ(1, eventLog.size()); + EXPECT_EQ("Init", eventLog[0]); + + // 运行几帧 + for (int i = 0; i < 3; i++) { + context->tick(0.016f); + } + + EXPECT_EQ(7, eventLog.size()); // Init + 3*(Update + LateUpdate) + EXPECT_EQ("Update", eventLog[1]); + EXPECT_EQ("LateUpdate", eventLog[2]); + + // 关闭 + context->shutdown(); + EXPECT_EQ(8, eventLog.size()); + EXPECT_EQ("Shutdown", eventLog[7]); +} + +// ============================================================================ +// 模块和插件集成测试 +// ============================================================================ + +TEST(Context, ModuleRegistration) { + auto context = Context::create(); + + // 创建一个测试模块 + class TestModule : public IModule { + public: + const char* name() const override { return "TestModule"; } + ModuleType type() const override { return ModuleType::Feature; } + + bool init() override { + initCalled = true; + return true; + } + + void shutdown() override { + shutdownCalled = true; + } + + bool initCalled = false; + bool shutdownCalled = false; + }; + + TestModule testModule; + + // 在初始化前注册模块 + context->modules().registerModule(&testModule); + + context->init(); + + EXPECT_TRUE(testModule.initCalled); + + context->shutdown(); + + EXPECT_TRUE(testModule.shutdownCalled); +} + +TEST(Context, PluginRegistration) { + auto context = Context::create(); + + // 创建一个测试插件 + class TestPlugin : public IPlugin { + public: + const PluginInfo& getInfo() const override { return info_; } + + bool load() override { + loadCalled = true; + return true; + } + + void unload() override { + unloadCalled = true; + } + + PluginInfo info_{"TestPlugin", "1.0.0"}; + bool loadCalled = false; + bool unloadCalled = false; + }; + + TestPlugin testPlugin; + + // 在初始化前注册插件 + context->plugins().registerPlugin(&testPlugin); + + context->init(); + + EXPECT_TRUE(testPlugin.loadCalled); + + context->shutdown(); + + EXPECT_TRUE(testPlugin.unloadCalled); +} + +// ============================================================================ +// 性能测试 +// ============================================================================ + +TEST(Context, HighFrequencyTick) { + auto context = Context::create(); + context->init(); + + int updateCount = 0; + + Listener listener; + listener.bind([&](float dt) { + updateCount++; + }); + + const int iterations = 1000; + for (int i = 0; i < iterations; i++) { + context->tick(0.016f); + } + + EXPECT_EQ(iterations, updateCount); + EXPECT_EQ(iterations, context->frameCount()); + + context->shutdown(); +} + +TEST(Context, MultipleTimers) { + auto context = Context::create(); + context->init(); + + int count1 = 0, count2 = 0, count3 = 0; + + context->timer().scheduleOnce(0.05f, [&]() { count1++; }); + context->timer().scheduleOnce(0.1f, [&]() { count2++; }); + context->timer().scheduleRepeat(0.03f, 5, [&]() { count3++; }); + + // 运行足够长的时间 + for (int i = 0; i < 20; i++) { + context->tick(0.016f); + } + + EXPECT_EQ(1, count1); + EXPECT_EQ(1, count2); + EXPECT_EQ(5, count3); + + context->shutdown(); +} diff --git a/tests/test_event_bus.cpp b/tests/test_event_bus.cpp new file mode 100644 index 0000000..0b81db0 --- /dev/null +++ b/tests/test_event_bus.cpp @@ -0,0 +1,534 @@ +#include +#include +#include + +using namespace extra2d; +using extra2d::event::broadcast; +using extra2d::event::Listener; + +// ============================================================================ +// 基础事件测试 +// ============================================================================ + +TEST(EventBus, BasicBroadcast) { + int received = 0; + + { + Listener listener; + listener.bind([&received]() { + received++; + }); + + // 广播事件 + broadcast(); + EXPECT_EQ(1, received); + + // 再次广播 + broadcast(); + EXPECT_EQ(2, received); + } + + // 监听器销毁后不应再接收 + broadcast(); + EXPECT_EQ(2, received); +} + +TEST(EventBus, MultipleListeners) { + int count1 = 0, count2 = 0, count3 = 0; + + Listener listener1; + listener1.bind([&count1](float dt) { + count1++; + }); + + Listener listener2; + listener2.bind([&count2](float dt) { + count2++; + }); + + Listener listener3; + listener3.bind([&count3](float dt) { + count3++; + }); + + broadcast(0.016f); + + EXPECT_EQ(1, count1); + EXPECT_EQ(1, count2); + EXPECT_EQ(1, count3); +} + +TEST(EventBus, ListenerEnableDisable) { + int count = 0; + + Listener listener; + listener.bind([&count]() { + count++; + }); + + // 默认启用 + broadcast(); + EXPECT_EQ(1, count); + + // 禁用 + listener.disable(); + EXPECT_FALSE(listener.isEnabled()); + broadcast(); + EXPECT_EQ(1, count); // 不应增加 + + // 重新启用 + listener.enable(); + EXPECT_TRUE(listener.isEnabled()); + broadcast(); + EXPECT_EQ(2, count); +} + +TEST(EventBus, ListenerReset) { + int count = 0; + + Listener listener; + listener.bind([&count]() { + count++; + }); + + broadcast(); + EXPECT_EQ(1, count); + + // 重置回调 + listener.reset(); + broadcast(); + EXPECT_EQ(1, count); // 不应增加 +} + +// ============================================================================ +// 带参数事件测试 +// ============================================================================ + +TEST(EventBus, EventWithParameters) { + float receivedDt = 0.0f; + + Listener listener; + listener.bind([&receivedDt](float dt) { + receivedDt = dt; + }); + + broadcast(0.016f); + EXPECT_NEAR(0.016f, receivedDt, 0.0001f); + + broadcast(0.033f); + EXPECT_NEAR(0.033f, receivedDt, 0.0001f); +} + +TEST(EventBus, EventWithMultipleParameters) { + int receivedWidth = 0, receivedHeight = 0; + + Listener listener; + listener.bind([&receivedWidth, &receivedHeight](int32 width, int32 height) { + receivedWidth = width; + receivedHeight = height; + }); + + broadcast(1920, 1080); + EXPECT_EQ(1920, receivedWidth); + EXPECT_EQ(1080, receivedHeight); + + broadcast(1280, 720); + EXPECT_EQ(1280, receivedWidth); + EXPECT_EQ(720, receivedHeight); +} + +// ============================================================================ +// 不同事件类型测试 +// ============================================================================ + +TEST(EventBus, DifferentEventTypes) { + int initCount = 0; + int updateCount = 0; + int shutdownCount = 0; + + Listener listener1; + listener1.bind([&initCount]() { + initCount++; + }); + + Listener listener2; + listener2.bind([&updateCount](float dt) { + updateCount++; + }); + + Listener listener3; + listener3.bind([&shutdownCount]() { + shutdownCount++; + }); + + // 广播不同类型的事件 + broadcast(); + EXPECT_EQ(1, initCount); + EXPECT_EQ(0, updateCount); + EXPECT_EQ(0, shutdownCount); + + broadcast(0.016f); + EXPECT_EQ(1, initCount); + EXPECT_EQ(1, updateCount); + EXPECT_EQ(0, shutdownCount); + + broadcast(); + EXPECT_EQ(1, initCount); + EXPECT_EQ(1, updateCount); + EXPECT_EQ(1, shutdownCount); +} + +// ============================================================================ +// 监听器生命周期测试 +// ============================================================================ + +TEST(EventBus, ListenerAutoUnregister) { + int count = 0; + + { + Listener listener; + listener.bind([&count]() { + count++; + }); + + broadcast(); + EXPECT_EQ(1, count); + } // listener 在这里销毁 + + // 再次广播,不应触发 + broadcast(); + EXPECT_EQ(1, count); +} + +TEST(EventBus, ListenerOrder) { + std::vector order; + + Listener listener1; + listener1.bind([&order]() { + order.push_back(1); + }); + + Listener listener2; + listener2.bind([&order]() { + order.push_back(2); + }); + + Listener listener3; + listener3.bind([&order]() { + order.push_back(3); + }); + + broadcast(); + + EXPECT_EQ(3, order.size()); + EXPECT_EQ(1, order[0]); + EXPECT_EQ(2, order[1]); + EXPECT_EQ(3, order[2]); +} + +// ============================================================================ +// 复杂场景测试 +// ============================================================================ + +TEST(EventBus, AddListenerDuringBroadcast) { + // 这个测试验证在广播期间添加监听器的行为 + int count1 = 0; + int count2 = 0; + + Listener* listener2 = nullptr; + + Listener listener1; + listener1.bind([&]() { + count1++; + // 在回调中创建新监听器 + if (!listener2) { + listener2 = new Listener(); + listener2->bind([&count2]() { + count2++; + }); + } + }); + + broadcast(); + EXPECT_EQ(1, count1); + // 新监听器在当前广播中不应被触发 + EXPECT_EQ(0, count2); + + // 下一次广播,新监听器应该被触发 + broadcast(); + EXPECT_EQ(2, count1); + EXPECT_EQ(1, count2); + + delete listener2; +} + +TEST(EventBus, RemoveListenerDuringBroadcast) { + int count1 = 0, count2 = 0, count3 = 0; + + Listener listener1; + listener1.bind([&]() { + count1++; + }); + + { + Listener listener2; + listener2.bind([&]() { + count2++; + }); + + Listener listener3; + listener3.bind([&]() { + count3++; + }); + + broadcast(); + EXPECT_EQ(1, count1); + EXPECT_EQ(1, count2); + EXPECT_EQ(1, count3); + } // listener2 和 listener3 在这里销毁 + + // 再次广播 + broadcast(); + EXPECT_EQ(2, count1); + EXPECT_EQ(1, count2); // 不应增加 + EXPECT_EQ(1, count3); // 不应增加 +} + +// ============================================================================ +// 输入事件测试 +// ============================================================================ + +TEST(EventBus, InputEvents) { + int keyDownCode = -1; + int keyUpCode = -1; + int mouseButton = -1; + int mouseX = -1, mouseY = -1; + + Listener keyDownListener; + keyDownListener.bind([&keyDownCode](int32 key) { + keyDownCode = key; + }); + + Listener keyUpListener; + keyUpListener.bind([&keyUpCode](int32 key) { + keyUpCode = key; + }); + + Listener mouseDownListener; + mouseDownListener.bind([&mouseButton, &mouseX, &mouseY](int32 button, int32 x, int32 y) { + mouseButton = button; + mouseX = x; + mouseY = y; + }); + + // 模拟输入事件 + broadcast(65); // 'A' key + EXPECT_EQ(65, keyDownCode); + + broadcast(65); + EXPECT_EQ(65, keyUpCode); + + broadcast(0, 100, 200); // 左键点击 + EXPECT_EQ(0, mouseButton); + EXPECT_EQ(100, mouseX); + EXPECT_EQ(200, mouseY); +} + +TEST(EventBus, MouseMoveEvent) { + int receivedX = -1, receivedY = -1; + + Listener listener; + listener.bind([&receivedX, &receivedY](int32 x, int32 y) { + receivedX = x; + receivedY = y; + }); + + broadcast(500, 300); + EXPECT_EQ(500, receivedX); + EXPECT_EQ(300, receivedY); +} + +TEST(EventBus, MouseWheelEvent) { + int receivedDelta = 0; + + Listener listener; + listener.bind([&receivedDelta](int32 delta) { + receivedDelta = delta; + }); + + broadcast(120); + EXPECT_EQ(120, receivedDelta); + + broadcast(-120); + EXPECT_EQ(-120, receivedDelta); +} + +// ============================================================================ +// 窗口事件测试 +// ============================================================================ + +TEST(EventBus, WindowEvents) { + bool focusReceived = false; + bool blurReceived = false; + bool showReceived = false; + bool hideReceived = false; + bool closeReceived = false; + int resizeWidth = 0, resizeHeight = 0; + + Listener focusListener; + focusListener.bind([&focusReceived]() { + focusReceived = true; + }); + + Listener blurListener; + blurListener.bind([&blurReceived]() { + blurReceived = true; + }); + + Listener showListener; + showListener.bind([&showReceived]() { + showReceived = true; + }); + + Listener hideListener; + hideListener.bind([&hideReceived]() { + hideReceived = true; + }); + + Listener closeListener; + closeListener.bind([&closeReceived]() { + closeReceived = true; + }); + + Listener resizeListener; + resizeListener.bind([&resizeWidth, &resizeHeight](int32 width, int32 height) { + resizeWidth = width; + resizeHeight = height; + }); + + broadcast(); + EXPECT_TRUE(focusReceived); + + broadcast(); + EXPECT_TRUE(blurReceived); + + broadcast(); + EXPECT_TRUE(showReceived); + + broadcast(); + EXPECT_TRUE(hideReceived); + + broadcast(); + EXPECT_TRUE(closeReceived); + + broadcast(800, 600); + EXPECT_EQ(800, resizeWidth); + EXPECT_EQ(600, resizeHeight); +} + +// ============================================================================ +// 触摸事件测试 +// ============================================================================ + +TEST(EventBus, TouchEvents) { + int touchId = -1; + int touchX = -1, touchY = -1; + + Listener touchBeginListener; + touchBeginListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) { + touchId = id; + touchX = x; + touchY = y; + }); + + broadcast(0, 100, 200); + EXPECT_EQ(0, touchId); + EXPECT_EQ(100, touchX); + EXPECT_EQ(200, touchY); + + Listener touchMoveListener; + touchMoveListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) { + touchId = id; + touchX = x; + touchY = y; + }); + + broadcast(0, 150, 250); + EXPECT_EQ(0, touchId); + EXPECT_EQ(150, touchX); + EXPECT_EQ(250, touchY); + + Listener touchEndListener; + touchEndListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) { + touchId = id; + touchX = x; + touchY = y; + }); + + broadcast(0, 150, 250); + EXPECT_EQ(0, touchId); + EXPECT_EQ(150, touchX); + EXPECT_EQ(250, touchY); +} + +// ============================================================================ +// 游戏手柄事件测试 +// ============================================================================ + +TEST(EventBus, GamepadEvents) { + int connectedDeviceId = -1; + int disconnectedDeviceId = -1; + + Listener connectListener; + connectListener.bind([&connectedDeviceId](int32 deviceId) { + connectedDeviceId = deviceId; + }); + + Listener disconnectListener; + disconnectListener.bind([&disconnectedDeviceId](int32 deviceId) { + disconnectedDeviceId = deviceId; + }); + + broadcast(0); + EXPECT_EQ(0, connectedDeviceId); + + broadcast(0); + EXPECT_EQ(0, disconnectedDeviceId); +} + +// ============================================================================ +// 性能测试 +// ============================================================================ + +TEST(EventBus, HighFrequencyBroadcast) { + const int iterations = 10000; + int count = 0; + + Listener listener; + listener.bind([&count](float dt) { + count++; + }); + + for (int i = 0; i < iterations; i++) { + broadcast(0.016f); + } + + EXPECT_EQ(iterations, count); +} + +TEST(EventBus, ManyListeners) { + const int listenerCount = 100; + int totalCount = 0; + + std::vector>> listeners; + for (int i = 0; i < listenerCount; i++) { + auto listener = std::make_unique>(); + listener->bind([&totalCount]() { + totalCount++; + }); + listeners.push_back(std::move(listener)); + } + + broadcast(); + EXPECT_EQ(listenerCount, totalCount); +} diff --git a/tests/test_module_registry.cpp b/tests/test_module_registry.cpp new file mode 100644 index 0000000..e3e0f21 --- /dev/null +++ b/tests/test_module_registry.cpp @@ -0,0 +1,322 @@ +#include +#include +#include +#include + +using namespace extra2d; + +// ============================================================================ +// 测试用模块类 +// ============================================================================ + +class TestModule : public IModule { +public: + TestModule(const char* name, ModuleType type, int priority) + : name_(name), type_(type), priority_(priority) {} + + const char* name() const override { return name_.c_str(); } + ModuleType type() const override { return type_; } + int priority() const override { return priority_; } + + bool init() override { + initCalled = true; + return initResult; + } + + void shutdown() override { + shutdownCalled = true; + } + + std::string name_; + ModuleType type_; + int priority_; + bool initCalled = false; + bool shutdownCalled = false; + bool initResult = true; +}; + +// ============================================================================ +// ModuleRegistry 基础测试 +// ============================================================================ + +TEST(ModuleRegistry, BasicOperations) { + ModuleRegistry registry; + + TestModule module1("Module1", ModuleType::Feature, 0); + TestModule module2("Module2", ModuleType::System, 10); + + // 注册模块 + registry.registerModule(&module1); + EXPECT_EQ(1, registry.getModuleCount()); + EXPECT_TRUE(registry.hasModule("Module1")); + + registry.registerModule(&module2); + EXPECT_EQ(2, registry.getModuleCount()); + EXPECT_TRUE(registry.hasModule("Module2")); + + // 获取模块 + EXPECT_EQ(&module1, registry.getModule("Module1")); + EXPECT_EQ(&module2, registry.getModule("Module2")); + EXPECT_EQ(nullptr, registry.getModule("NonExistent")); + + // 注销模块 + registry.unregisterModule("Module1"); + EXPECT_EQ(1, registry.getModuleCount()); + EXPECT_FALSE(registry.hasModule("Module1")); + EXPECT_TRUE(registry.hasModule("Module2")); +} + +TEST(ModuleRegistry, DuplicateRegistration) { + ModuleRegistry registry; + + TestModule module1("SameName", ModuleType::Feature, 0); + TestModule module2("SameName", ModuleType::System, 10); + + registry.registerModule(&module1); + EXPECT_EQ(1, registry.getModuleCount()); + + // 重复注册同名模块,后注册的应覆盖 + registry.registerModule(&module2); + EXPECT_EQ(1, registry.getModuleCount()); + EXPECT_EQ(&module2, registry.getModule("SameName")); +} + +TEST(ModuleRegistry, GetAllModules) { + ModuleRegistry registry; + + TestModule module1("Module1", ModuleType::Feature, 0); + TestModule module2("Module2", ModuleType::System, 10); + TestModule module3("Module3", ModuleType::Core, 5); + + registry.registerModule(&module1); + registry.registerModule(&module2); + registry.registerModule(&module3); + + auto allModules = registry.getAllModules(); + EXPECT_EQ(3, allModules.size()); +} + +TEST(ModuleRegistry, GetModulesByType) { + ModuleRegistry registry; + + TestModule coreModule("CoreModule", ModuleType::Core, 0); + TestModule systemModule1("SystemModule1", ModuleType::System, 10); + TestModule systemModule2("SystemModule2", ModuleType::System, 20); + TestModule featureModule("FeatureModule", ModuleType::Feature, 5); + + registry.registerModule(&coreModule); + registry.registerModule(&systemModule1); + registry.registerModule(&systemModule2); + registry.registerModule(&featureModule); + + auto coreModules = registry.getModulesByType(ModuleType::Core); + EXPECT_EQ(1, coreModules.size()); + + auto systemModules = registry.getModulesByType(ModuleType::System); + EXPECT_EQ(2, systemModules.size()); + + auto featureModules = registry.getModulesByType(ModuleType::Feature); + EXPECT_EQ(1, featureModules.size()); + + auto extensionModules = registry.getModulesByType(ModuleType::Extension); + EXPECT_EQ(0, extensionModules.size()); +} + +// ============================================================================ +// 初始化和关闭测试 +// ============================================================================ + +TEST(ModuleRegistry, InitShutdown) { + ModuleRegistry registry; + + TestModule module1("Module1", ModuleType::Feature, 0); + TestModule module2("Module2", ModuleType::System, 10); + + registry.registerModule(&module1); + registry.registerModule(&module2); + + // 初始化所有模块 + EXPECT_TRUE(registry.initAll()); + EXPECT_TRUE(module1.initCalled); + EXPECT_TRUE(module2.initCalled); + + // 关闭所有模块 + registry.shutdownAll(); + EXPECT_TRUE(module1.shutdownCalled); + EXPECT_TRUE(module2.shutdownCalled); +} + +TEST(ModuleRegistry, InitFailure) { + ModuleRegistry registry; + + TestModule module1("Module1", ModuleType::Feature, 0); + TestModule module2("Module2", ModuleType::System, 10); + module2.initResult = false; // 模拟初始化失败 + + registry.registerModule(&module1); + registry.registerModule(&module2); + + // 初始化应失败 + EXPECT_FALSE(registry.initAll()); + EXPECT_TRUE(module1.initCalled); + EXPECT_TRUE(module2.initCalled); +} + +TEST(ModuleRegistry, PriorityOrder) { + ModuleRegistry registry; + + std::vector initOrder; + std::vector shutdownOrder; + + class OrderTrackingModule : public IModule { + public: + OrderTrackingModule(const char* name, int priority, + std::vector& initVec, + std::vector& shutdownVec) + : name_(name), priority_(priority), + initOrder_(initVec), shutdownOrder_(shutdownVec) {} + + const char* name() const override { return name_.c_str(); } + int priority() const override { return priority_; } + + bool init() override { + initOrder_.push_back(name_); + return true; + } + + void shutdown() override { + shutdownOrder_.push_back(name_); + } + + std::string name_; + int priority_; + std::vector& initOrder_; + std::vector& shutdownOrder_; + }; + + OrderTrackingModule module1("LowPriority", 100, initOrder, shutdownOrder); + OrderTrackingModule module2("HighPriority", 10, initOrder, shutdownOrder); + OrderTrackingModule module3("MediumPriority", 50, initOrder, shutdownOrder); + + registry.registerModule(&module1); + registry.registerModule(&module2); + registry.registerModule(&module3); + + registry.initAll(); + + // 初始化应按优先级顺序(数值小的先初始化) + EXPECT_EQ(3, initOrder.size()); + EXPECT_EQ("HighPriority", initOrder[0]); + EXPECT_EQ("MediumPriority", initOrder[1]); + EXPECT_EQ("LowPriority", initOrder[2]); + + registry.shutdownAll(); + + // 关闭应按优先级逆序(数值大的先关闭) + EXPECT_EQ(3, shutdownOrder.size()); + EXPECT_EQ("LowPriority", shutdownOrder[0]); + EXPECT_EQ("MediumPriority", shutdownOrder[1]); + EXPECT_EQ("HighPriority", shutdownOrder[2]); +} + +// ============================================================================ +// 空注册表测试 +// ============================================================================ + +TEST(ModuleRegistry, EmptyRegistry) { + ModuleRegistry registry; + + EXPECT_EQ(0, registry.getModuleCount()); + EXPECT_FALSE(registry.hasModule("AnyModule")); + EXPECT_EQ(nullptr, registry.getModule("AnyModule")); + + auto allModules = registry.getAllModules(); + EXPECT_EQ(0, allModules.size()); + + // 空注册表的初始化和关闭不应崩溃 + EXPECT_TRUE(registry.initAll()); + registry.shutdownAll(); +} + +TEST(ModuleRegistry, UnregisterNonExistent) { + ModuleRegistry registry; + + // 注销不存在的模块不应崩溃 + EXPECT_NO_THROW(registry.unregisterModule("NonExistent")); +} + +// ============================================================================ +// 模块生命周期测试 +// ============================================================================ + +TEST(ModuleRegistry, ModuleLifecycle) { + ModuleRegistry registry; + + enum class State { Created, Initialized, Running, Shutdown }; + + class LifecycleModule : public IModule { + public: + LifecycleModule(const char* name, State& state) + : name_(name), state_(state) { + state_ = State::Created; + } + + const char* name() const override { return name_.c_str(); } + + bool init() override { + state_ = State::Initialized; + return true; + } + + void shutdown() override { + state_ = State::Shutdown; + } + + std::string name_; + State& state_; + }; + + State state = State::Created; + LifecycleModule module("Lifecycle", state); + + EXPECT_EQ(State::Created, state); + + registry.registerModule(&module); + registry.initAll(); + EXPECT_EQ(State::Initialized, state); + + registry.shutdownAll(); + EXPECT_EQ(State::Shutdown, state); +} + +// ============================================================================ +// 混合类型模块测试 +// ============================================================================ + +TEST(ModuleRegistry, MixedModuleTypes) { + ModuleRegistry registry; + + TestModule coreModule("Core", ModuleType::Core, Pri::System); + TestModule systemModule("System", ModuleType::System, Pri::Window); + TestModule featureModule("Feature", ModuleType::Feature, 0); + TestModule extensionModule("Extension", ModuleType::Extension, 100); + + registry.registerModule(&coreModule); + registry.registerModule(&systemModule); + registry.registerModule(&featureModule); + registry.registerModule(&extensionModule); + + EXPECT_EQ(4, registry.getModuleCount()); + + EXPECT_TRUE(registry.initAll()); + EXPECT_TRUE(coreModule.initCalled); + EXPECT_TRUE(systemModule.initCalled); + EXPECT_TRUE(featureModule.initCalled); + EXPECT_TRUE(extensionModule.initCalled); + + registry.shutdownAll(); + EXPECT_TRUE(coreModule.shutdownCalled); + EXPECT_TRUE(systemModule.shutdownCalled); + EXPECT_TRUE(featureModule.shutdownCalled); + EXPECT_TRUE(extensionModule.shutdownCalled); +} diff --git a/tests/test_plugin_loader.cpp b/tests/test_plugin_loader.cpp new file mode 100644 index 0000000..3a67968 --- /dev/null +++ b/tests/test_plugin_loader.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include + +using namespace extra2d; + +// ============================================================================ +// 测试用插件类 +// ============================================================================ + +class TestPlugin : public IPlugin { +public: + TestPlugin(const char* name, const char* version = "1.0.0", + const std::vector& deps = {}) + : name_(name), version_(version), dependencies_(deps) { + info_.name = name; + info_.version = version; + info_.dependencies = deps; + } + + const PluginInfo& getInfo() const override { return info_; } + + bool load() override { + loadCalled = true; + return loadResult; + } + + void unload() override { + unloadCalled = true; + } + + std::vector getDependencies() const override { + return dependencies_; + } + + std::string name_; + std::string version_; + std::vector dependencies_; + PluginInfo info_; + bool loadCalled = false; + bool unloadCalled = false; + bool loadResult = true; +}; + +// ============================================================================ +// PluginLoader 基础测试 +// ============================================================================ + +TEST(PluginLoader, BasicOperations) { + PluginLoader loader; + + TestPlugin plugin1("Plugin1"); + TestPlugin plugin2("Plugin2", "2.0.0"); + + // 注册插件 + loader.registerPlugin(&plugin1); + EXPECT_EQ(1, loader.getPluginCount()); + EXPECT_TRUE(loader.hasPlugin("Plugin1")); + + loader.registerPlugin(&plugin2); + EXPECT_EQ(2, loader.getPluginCount()); + EXPECT_TRUE(loader.hasPlugin("Plugin2")); + + // 获取插件 + EXPECT_EQ(&plugin1, loader.getPlugin("Plugin1")); + EXPECT_EQ(&plugin2, loader.getPlugin("Plugin2")); + EXPECT_EQ(nullptr, loader.getPlugin("NonExistent")); + + // 卸载插件 + loader.unloadPlugin("Plugin1"); + EXPECT_EQ(1, loader.getPluginCount()); + EXPECT_FALSE(loader.hasPlugin("Plugin1")); + EXPECT_TRUE(loader.hasPlugin("Plugin2")); +} + +TEST(PluginLoader, DuplicateRegistration) { + PluginLoader loader; + + TestPlugin plugin1("SameName", "1.0.0"); + TestPlugin plugin2("SameName", "2.0.0"); + + loader.registerPlugin(&plugin1); + EXPECT_EQ(1, loader.getPluginCount()); + + // 重复注册同名插件,后注册的应覆盖 + loader.registerPlugin(&plugin2); + EXPECT_EQ(1, loader.getPluginCount()); + + IPlugin* retrieved = loader.getPlugin("SameName"); + EXPECT_NE(nullptr, retrieved); + EXPECT_EQ("2.0.0", retrieved->getInfo().version); +} + +TEST(PluginLoader, GetAllPlugins) { + PluginLoader loader; + + TestPlugin plugin1("Plugin1"); + TestPlugin plugin2("Plugin2"); + TestPlugin plugin3("Plugin3"); + + loader.registerPlugin(&plugin1); + loader.registerPlugin(&plugin2); + loader.registerPlugin(&plugin3); + + auto allPlugins = loader.getAllPlugins(); + EXPECT_EQ(3, allPlugins.size()); +} + +// ============================================================================ +// 加载和卸载测试 +// ============================================================================ + +TEST(PluginLoader, LoadUnload) { + PluginLoader loader; + + TestPlugin plugin1("Plugin1"); + TestPlugin plugin2("Plugin2"); + + loader.registerPlugin(&plugin1); + loader.registerPlugin(&plugin2); + + // 加载所有插件 + EXPECT_TRUE(loader.initAll()); + EXPECT_TRUE(plugin1.loadCalled); + EXPECT_TRUE(plugin2.loadCalled); + + // 卸载所有插件 + loader.shutdownAll(); + EXPECT_TRUE(plugin1.unloadCalled); + EXPECT_TRUE(plugin2.unloadCalled); +} + +TEST(PluginLoader, LoadFailure) { + PluginLoader loader; + + TestPlugin plugin1("Plugin1"); + TestPlugin plugin2("Plugin2"); + plugin2.loadResult = false; // 模拟加载失败 + + loader.registerPlugin(&plugin1); + loader.registerPlugin(&plugin2); + + // 加载应失败 + EXPECT_FALSE(loader.initAll()); + EXPECT_TRUE(plugin1.loadCalled); + EXPECT_TRUE(plugin2.loadCalled); +} + +// ============================================================================ +// 依赖关系测试 +// ============================================================================ + +TEST(PluginLoader, Dependencies) { + PluginLoader loader; + + // PluginC 依赖 PluginB,PluginB 依赖 PluginA + TestPlugin pluginA("PluginA"); + TestPlugin pluginB("PluginB", "1.0.0", {"PluginA"}); + TestPlugin pluginC("PluginC", "1.0.0", {"PluginB"}); + + loader.registerPlugin(&pluginC); + loader.registerPlugin(&pluginB); + loader.registerPlugin(&pluginA); + + // 所有插件都应能加载(依赖会自动解析) + EXPECT_TRUE(loader.initAll()); + EXPECT_TRUE(pluginA.loadCalled); + EXPECT_TRUE(pluginB.loadCalled); + EXPECT_TRUE(pluginC.loadCalled); + + loader.shutdownAll(); +} + +TEST(PluginLoader, MissingDependency) { + PluginLoader loader; + + // PluginB 依赖不存在的 PluginA + TestPlugin pluginB("PluginB", "1.0.0", {"PluginA"}); + + loader.registerPlugin(&pluginB); + + // 加载应失败(依赖未满足) + EXPECT_FALSE(loader.initAll()); + EXPECT_FALSE(pluginB.loadCalled); +} + +TEST(PluginLoader, CircularDependency) { + PluginLoader loader; + + // A 依赖 B,B 依赖 A(循环依赖) + TestPlugin pluginA("PluginA", "1.0.0", {"PluginB"}); + TestPlugin pluginB("PluginB", "1.0.0", {"PluginA"}); + + loader.registerPlugin(&pluginA); + loader.registerPlugin(&pluginB); + + // 循环依赖应能处理(目前实现可能不检测循环,但至少不应崩溃) + // 注意:根据实现不同,这可能成功或失败 + loader.initAll(); + loader.shutdownAll(); +} + +TEST(PluginLoader, DependencyLoadOrder) { + PluginLoader loader; + + std::vector loadOrder; + std::vector unloadOrder; + + class OrderTrackingPlugin : public IPlugin { + public: + OrderTrackingPlugin(const char* name, + const std::vector& deps, + std::vector& loadVec, + std::vector& unloadVec) + : name_(name), loadOrder_(loadVec), unloadOrder_(unloadVec) { + info_.name = name; + info_.dependencies = deps; + } + + const PluginInfo& getInfo() const override { return info_; } + + bool load() override { + loadOrder_.push_back(name_); + return true; + } + + void unload() override { + unloadOrder_.push_back(name_); + } + + std::string name_; + PluginInfo info_; + std::vector& loadOrder_; + std::vector& unloadOrder_; + }; + + // C 依赖 B,B 依赖 A + OrderTrackingPlugin pluginC("PluginC", {"PluginB"}, loadOrder, unloadOrder); + OrderTrackingPlugin pluginA("PluginA", {}, loadOrder, unloadOrder); + OrderTrackingPlugin pluginB("PluginB", {"PluginA"}, loadOrder, unloadOrder); + + loader.registerPlugin(&pluginC); + loader.registerPlugin(&pluginA); + loader.registerPlugin(&pluginB); + + loader.initAll(); + + // 加载顺序应满足依赖关系 + EXPECT_EQ(3, loadOrder.size()); + // A 必须在 B 之前加载 + auto aPos = std::find(loadOrder.begin(), loadOrder.end(), "PluginA"); + auto bPos = std::find(loadOrder.begin(), loadOrder.end(), "PluginB"); + auto cPos = std::find(loadOrder.begin(), loadOrder.end(), "PluginC"); + EXPECT_TRUE(aPos < bPos); + EXPECT_TRUE(bPos < cPos); + + loader.shutdownAll(); + + // 卸载顺序应与加载顺序相反 + EXPECT_EQ(3, unloadOrder.size()); +} + +// ============================================================================ +// 空加载器测试 +// ============================================================================ + +TEST(PluginLoader, EmptyLoader) { + PluginLoader loader; + + EXPECT_EQ(0, loader.getPluginCount()); + EXPECT_FALSE(loader.hasPlugin("AnyPlugin")); + EXPECT_EQ(nullptr, loader.getPlugin("AnyPlugin")); + + auto allPlugins = loader.getAllPlugins(); + EXPECT_EQ(0, allPlugins.size()); + + // 空加载器的初始化和关闭不应崩溃 + EXPECT_TRUE(loader.initAll()); + loader.shutdownAll(); +} + +TEST(PluginLoader, UnloadNonExistent) { + PluginLoader loader; + + // 卸载不存在的插件不应崩溃 + EXPECT_NO_THROW(loader.unloadPlugin("NonExistent")); +} + +// ============================================================================ +// 搜索路径测试 +// ============================================================================ + +TEST(PluginLoader, SearchPaths) { + PluginLoader loader; + + // 添加搜索路径 + loader.addSearchPath("/usr/lib/extra2d/plugins"); + loader.addSearchPath("/home/user/.extra2d/plugins"); + loader.addSearchPath("./plugins"); + + // 搜索路径功能主要用于动态库加载,这里只验证不崩溃 + EXPECT_NO_THROW(loader.loadPluginsFromDirectory("./non_existent_dir")); +} + +// ============================================================================ +// 插件信息测试 +// ============================================================================ + +TEST(PluginLoader, PluginInfo) { + PluginLoader loader; + + TestPlugin plugin("TestPlugin", "1.2.3"); + plugin.info_.author = "Test Author"; + plugin.info_.description = "Test Description"; + plugin.info_.dependencies = {"Dep1", "Dep2"}; + + loader.registerPlugin(&plugin); + + IPlugin* retrieved = loader.getPlugin("TestPlugin"); + EXPECT_NE(nullptr, retrieved); + + const PluginInfo& info = retrieved->getInfo(); + EXPECT_EQ("TestPlugin", info.name); + EXPECT_EQ("1.2.3", info.version); + EXPECT_EQ("Test Author", info.author); + EXPECT_EQ("Test Description", info.description); + EXPECT_EQ(2, info.dependencies.size()); + EXPECT_EQ("Dep1", info.dependencies[0]); + EXPECT_EQ("Dep2", info.dependencies[1]); +} + +// ============================================================================ +// 插件生命周期测试 +// ============================================================================ + +TEST(PluginLoader, PluginLifecycle) { + PluginLoader loader; + + enum class State { Created, Loaded, Running, Unloaded }; + + class LifecyclePlugin : public IPlugin { + public: + LifecyclePlugin(const char* name, State& state) + : name_(name), state_(state) { + state_ = State::Created; + info_.name = name; + } + + const PluginInfo& getInfo() const override { return info_; } + + bool load() override { + state_ = State::Loaded; + return true; + } + + void unload() override { + state_ = State::Unloaded; + } + + std::string name_; + State& state_; + PluginInfo info_; + }; + + State state = State::Created; + LifecyclePlugin plugin("Lifecycle", state); + + EXPECT_EQ(State::Created, state); + + loader.registerPlugin(&plugin); + loader.initAll(); + EXPECT_EQ(State::Loaded, state); + + loader.shutdownAll(); + EXPECT_EQ(State::Unloaded, state); +} + +// ============================================================================ +// 复杂依赖测试 +// ============================================================================ + +TEST(PluginLoader, ComplexDependencies) { + PluginLoader loader; + + // 创建复杂的依赖图: + // A -> B -> D + // A -> C -> D + // E (独立) + + TestPlugin pluginA("A", "1.0.0", {}); + TestPlugin pluginB("B", "1.0.0", {"A"}); + TestPlugin pluginC("C", "1.0.0", {"A"}); + TestPlugin pluginD("D", "1.0.0", {"B", "C"}); + TestPlugin pluginE("E", "1.0.0", {}); + + // 乱序注册 + loader.registerPlugin(&pluginD); + loader.registerPlugin(&pluginB); + loader.registerPlugin(&pluginE); + loader.registerPlugin(&pluginC); + loader.registerPlugin(&pluginA); + + // 所有插件都应能加载 + EXPECT_TRUE(loader.initAll()); + EXPECT_TRUE(pluginA.loadCalled); + EXPECT_TRUE(pluginB.loadCalled); + EXPECT_TRUE(pluginC.loadCalled); + EXPECT_TRUE(pluginD.loadCalled); + EXPECT_TRUE(pluginE.loadCalled); + + loader.shutdownAll(); +} diff --git a/tests/test_timer_module.cpp b/tests/test_timer_module.cpp new file mode 100644 index 0000000..a51c728 --- /dev/null +++ b/tests/test_timer_module.cpp @@ -0,0 +1,405 @@ +#include +#include + +using namespace extra2d; + +// ============================================================================ +// TimerModule 基础测试 +// ============================================================================ + +TEST(TimerModule, BasicProperties) { + TimerModule timer; + + // 测试基本属性 + EXPECT_STREQ("Timer", timer.name()); + EXPECT_EQ(ModuleType::Core, timer.type()); + EXPECT_EQ(Pri::System, timer.priority()); +} + +TEST(TimerModule, InitShutdown) { + TimerModule timer; + + // 测试初始化和关闭 + EXPECT_TRUE(timer.init()); + timer.shutdown(); +} + +TEST(TimerModule, TimeScale) { + TimerModule timer; + + // 测试时间缩放默认值 + EXPECT_FLOAT_EQ(1.0f, timer.getTimeScale()); + + // 测试设置时间缩放 + timer.setTimeScale(2.0f); + EXPECT_FLOAT_EQ(2.0f, timer.getTimeScale()); + + timer.setTimeScale(0.5f); + EXPECT_FLOAT_EQ(0.5f, timer.getTimeScale()); + + // 测试边界值 + timer.setTimeScale(0.0f); + EXPECT_FLOAT_EQ(0.0f, timer.getTimeScale()); +} + +// ============================================================================ +// 一次性定时器测试 +// ============================================================================ + +TEST(TimerModule, ScheduleOnce) { + TimerModule timer; + timer.init(); + + bool called = false; + TimerId id = timer.scheduleOnce(0.1f, [&called]() { + called = true; + }); + + EXPECT_NE(INVALID_TIMER_ID, id); + EXPECT_EQ(1, timer.getActiveCount()); + + // 模拟时间流逝(未到达触发时间) + timer.update(0.05f); + EXPECT_FALSE(called); + EXPECT_EQ(1, timer.getActiveCount()); + + // 模拟时间流逝(到达触发时间) + timer.update(0.05f); + EXPECT_TRUE(called); + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +TEST(TimerModule, ScheduleOnceImmediate) { + TimerModule timer; + timer.init(); + + bool called = false; + TimerId id = timer.scheduleOnce(0.0f, [&called]() { + called = true; + }); + + EXPECT_NE(INVALID_TIMER_ID, id); + + // 立即触发 + timer.update(0.0f); + EXPECT_TRUE(called); + + timer.shutdown(); +} + +TEST(TimerModule, CancelTimer) { + TimerModule timer; + timer.init(); + + bool called = false; + TimerId id = timer.scheduleOnce(0.1f, [&called]() { + called = true; + }); + + // 取消定时器 + timer.cancel(id); + + // 模拟时间流逝 + timer.update(0.2f); + EXPECT_FALSE(called); + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +TEST(TimerModule, CancelInvalidTimer) { + TimerModule timer; + timer.init(); + + // 取消不存在的定时器不应崩溃 + EXPECT_NO_THROW(timer.cancel(99999)); + + timer.shutdown(); +} + +// ============================================================================ +// 重复定时器测试 +// ============================================================================ + +TEST(TimerModule, ScheduleRepeat) { + TimerModule timer; + timer.init(); + + int callCount = 0; + TimerId id = timer.scheduleRepeat(0.1f, 3, [&callCount]() { + callCount++; + }); + + EXPECT_NE(INVALID_TIMER_ID, id); + + // 第一次触发 + timer.update(0.1f); + EXPECT_EQ(1, callCount); + + // 第二次触发 + timer.update(0.1f); + EXPECT_EQ(2, callCount); + + // 第三次触发 + timer.update(0.1f); + EXPECT_EQ(3, callCount); + + // 定时器应已自动移除 + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +TEST(TimerModule, ScheduleRepeatInfinite) { + TimerModule timer; + timer.init(); + + int callCount = 0; + TimerId id = timer.scheduleRepeat(0.1f, 0, [&callCount]() { + callCount++; + }); + + // 触发多次 + for (int i = 0; i < 10; i++) { + timer.update(0.1f); + } + + EXPECT_EQ(10, callCount); + EXPECT_EQ(1, timer.getActiveCount()); + + // 取消无限定时器 + timer.cancel(id); + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +// ============================================================================ +// 每帧更新定时器测试 +// ============================================================================ + +TEST(TimerModule, ScheduleUpdate) { + TimerModule timer; + timer.init(); + + float totalDt = 0.0f; + int callCount = 0; + TimerId id = timer.scheduleUpdate([&](float dt) { + totalDt += dt; + callCount++; + }); + + EXPECT_NE(INVALID_TIMER_ID, id); + + // 模拟多帧 + timer.update(0.016f); + timer.update(0.016f); + timer.update(0.016f); + + EXPECT_EQ(3, callCount); + EXPECT_FLOAT_EQ(0.048f, totalDt); + + // 取消更新定时器 + timer.cancel(id); + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +// ============================================================================ +// 暂停和恢复测试 +// ============================================================================ + +TEST(TimerModule, PauseResume) { + TimerModule timer; + timer.init(); + + int callCount = 0; + TimerId id = timer.scheduleRepeat(0.1f, 0, [&callCount]() { + callCount++; + }); + + // 正常触发一次 + timer.update(0.1f); + EXPECT_EQ(1, callCount); + + // 暂停 + timer.pause(id); + timer.update(0.1f); + EXPECT_EQ(1, callCount); // 不应增加 + + // 恢复 + timer.resume(id); + timer.update(0.1f); + EXPECT_EQ(2, callCount); + + timer.shutdown(); +} + +TEST(TimerModule, PauseInvalidTimer) { + TimerModule timer; + timer.init(); + + // 暂停不存在的定时器不应崩溃 + EXPECT_NO_THROW(timer.pause(99999)); + EXPECT_NO_THROW(timer.resume(99999)); + + timer.shutdown(); +} + +// ============================================================================ +// 时间缩放测试 +// ============================================================================ + +TEST(TimerModule, TimeScaleEffect) { + TimerModule timer; + timer.init(); + + int callCount = 0; + timer.scheduleRepeat(1.0f, 0, [&callCount]() { + callCount++; + }); + + // 设置2倍速 + timer.setTimeScale(2.0f); + + // 实际经过0.5秒,但效果相当于1秒 + timer.update(0.5f); + EXPECT_EQ(1, callCount); + + timer.shutdown(); +} + +TEST(TimerModule, TimeScaleZero) { + TimerModule timer; + timer.init(); + + int callCount = 0; + timer.scheduleRepeat(0.1f, 0, [&callCount]() { + callCount++; + }); + + // 暂停时间 + timer.setTimeScale(0.0f); + + // 无论经过多少时间,都不会触发 + timer.update(1.0f); + timer.update(1.0f); + EXPECT_EQ(0, callCount); + + // 恢复时间 + timer.setTimeScale(1.0f); + timer.update(0.1f); + EXPECT_EQ(1, callCount); + + timer.shutdown(); +} + +// ============================================================================ +// 多定时器测试 +// ============================================================================ + +TEST(TimerModule, MultipleTimers) { + TimerModule timer; + timer.init(); + + int count1 = 0, count2 = 0, count3 = 0; + + TimerId id1 = timer.scheduleOnce(0.1f, [&count1]() { count1++; }); + TimerId id2 = timer.scheduleOnce(0.2f, [&count2]() { count2++; }); + TimerId id3 = timer.scheduleOnce(0.3f, [&count3]() { count3++; }); + + EXPECT_EQ(3, timer.getActiveCount()); + + // 经过0.15秒 + timer.update(0.15f); + EXPECT_EQ(1, count1); + EXPECT_EQ(0, count2); + EXPECT_EQ(0, count3); + EXPECT_EQ(2, timer.getActiveCount()); + + // 再经过0.1秒 + timer.update(0.1f); + EXPECT_EQ(1, count1); + EXPECT_EQ(1, count2); + EXPECT_EQ(0, count3); + EXPECT_EQ(1, timer.getActiveCount()); + + // 再经过0.1秒 + timer.update(0.1f); + EXPECT_EQ(1, count1); + EXPECT_EQ(1, count2); + EXPECT_EQ(1, count3); + EXPECT_EQ(0, timer.getActiveCount()); + + timer.shutdown(); +} + +TEST(TimerModule, CancelAll) { + TimerModule timer; + timer.init(); + + int count = 0; + timer.scheduleOnce(0.1f, [&count]() { count++; }); + timer.scheduleOnce(0.2f, [&count]() { count++; }); + timer.scheduleOnce(0.3f, [&count]() { count++; }); + + EXPECT_EQ(3, timer.getActiveCount()); + + // 取消所有 + timer.cancelAll(); + EXPECT_EQ(0, timer.getActiveCount()); + + // 确认都不会触发 + timer.update(0.5f); + EXPECT_EQ(0, count); + + timer.shutdown(); +} + +// ============================================================================ +// 边界条件测试 +// ============================================================================ + +TEST(TimerModule, SelfCancelInCallback) { + TimerModule timer; + timer.init(); + + TimerId id = INVALID_TIMER_ID; + bool called = false; + + // 在回调中取消自己 + id = timer.scheduleOnce(0.1f, [&]() { + called = true; + timer.cancel(id); + }); + + timer.update(0.1f); + EXPECT_TRUE(called); + + timer.shutdown(); +} + +TEST(TimerModule, ScheduleInCallback) { + TimerModule timer; + timer.init(); + + bool secondCalled = false; + + // 在回调中创建新定时器 + timer.scheduleOnce(0.1f, [&]() { + timer.scheduleOnce(0.1f, [&]() { + secondCalled = true; + }); + }); + + timer.update(0.1f); + EXPECT_FALSE(secondCalled); + + timer.update(0.1f); + EXPECT_TRUE(secondCalled); + + timer.shutdown(); +} diff --git a/tests/xmake.lua b/tests/xmake.lua new file mode 100644 index 0000000..0825000 --- /dev/null +++ b/tests/xmake.lua @@ -0,0 +1,59 @@ +-- Extra2D 测试套件构建脚本 +-- 使用 GoogleTest 标准测试框架 + +-- 添加 gtest 包依赖 +add_requires("gtest") + +target("extra2d_tests") + set_kind("binary") + set_languages("c++17") + + -- 添加 gtest 包 + add_packages("gtest") + + -- 添加测试源文件 + add_files("*.cpp") + + -- 添加头文件搜索路径 + add_includedirs(".") + add_includedirs("../include") + + -- 添加引擎源文件(仅核心模块,避免平台依赖) + add_files("../src/context/context.cpp") + add_files("../src/module/module_registry.cpp") + add_files("../src/module/timer_module.cpp") + add_files("../src/plugin/plugin_loader.cpp") + add_files("../src/event/event_bus.cpp") + add_files("../src/utils/random.cpp") + -- 注意:logger.cpp 在 TEST_BUILD 模式下使用条件编译避免 SDL 依赖 + + -- 第三方库 + add_includedirs("../third_party/nlohmann") + add_includedirs("../third_party/stb") + + -- 定义 TEST_BUILD 宏,用于条件编译 + add_defines("TEST_BUILD") + + -- 编译选项 + if is_mode("debug") then + add_defines("DEBUG") + set_symbols("debug") + set_optimize("none") + else + add_defines("NDEBUG") + set_optimize("fastest") + set_strip("all") + end + + -- 警告设置 + set_warnings("all") + + -- 平台特定设置 + if is_plat("windows") then + add_cxflags("/wd4267", "/wd4244", "/wd4996", "/wd4100", "/wd4458") + add_syslinks("kernel32", "user32") + elseif is_plat("mingw") then + add_cxflags("-Wno-unused-parameter", "-Wno-unused-variable") + else + add_cxflags("-Wno-unused-parameter", "-Wno-unused-variable") + end diff --git a/xmake.lua b/xmake.lua index 11229fd..7f9402d 100644 --- a/xmake.lua +++ b/xmake.lua @@ -32,6 +32,12 @@ option("debug_logs") set_description("Enable debug logging") option_end() +option("tests") + set_default(false) + set_showmenu(true) + set_description("Build test suite") +option_end() + -- ============================================== -- 平台检测与配置 -- ============================================== @@ -85,10 +91,15 @@ includes("xmake/engine.lua") -- 定义引擎库 define_extra2d_engine() --- 示例程序目标(作为子项目) -if is_config("examples","true") then - includes("examples/hello_world", {rootdir = "examples/hello_world"}) - includes("examples/collision_demo", {rootdir = "examples/collision_demo"}) +-- -- 示例程序目标(作为子项目) +-- if is_config("examples","true") then +-- includes("examples/hello_world", {rootdir = "examples/hello_world"}) +-- includes("examples/collision_demo", {rootdir = "examples/collision_demo"}) +-- end + +-- 测试套件目标(作为子项目) +if is_config("tests","true") then + includes("tests") end -- ============================================== @@ -102,4 +113,5 @@ print("Platform: " .. target_plat) print("Architecture: " .. (get_config("arch") or "auto")) print("Mode: " .. (is_mode("debug") and "debug" or "release")) print("Examples: " .. (has_config("examples") and "enabled" or "disabled")) +print("Tests: " .. (has_config("tests") and "enabled" or "disabled")) print("========================================")