feat(tests): 添加完整的测试套件框架和核心模块测试

refactor(module): 改进模块注册表同名模块处理逻辑
refactor(plugin): 优化插件加载器依赖检查机制
refactor(event): 重构事件总线监听器添加顺序逻辑

build: 添加测试构建选项和配置
docs: 添加测试套件README文档
This commit is contained in:
ChestnutYueyue 2026-02-28 21:48:35 +08:00
parent e68ce87638
commit 418d2c8f92
14 changed files with 2373 additions and 19 deletions

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <event/event_bus_macros.h> #include <event/event_bus_macros.h>
#include <platform/input.h>
#include <types/base/types.h> #include <types/base/types.h>

View File

@ -6,22 +6,34 @@
#include <type_traits> #include <type_traits>
#include <types/base/types.h> #include <types/base/types.h>
// SDL2 日志头文件 #ifndef TEST_BUILD
// SDL2 日志头文件(测试构建时不使用)
#include <SDL.h> #include <SDL.h>
#endif
namespace extra2d { namespace extra2d {
// ============================================================================ // ============================================================================
// 日志级别枚举 - 映射到 SDL_LogPriority // 日志级别枚举
// ============================================================================ // ============================================================================
enum class LogLevel { 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 详细日志 Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志 Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志 Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志 Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志 Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
Fatal = SDL_LOG_PRIORITY_CRITICAL, // 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<int>(level) < static_cast<int>(level_)) if (static_cast<int>(level) < static_cast<int>(level_))
return; return;
std::string msg = e2d_format(fmt, args...); 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, SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
static_cast<SDL_LogPriority>(level), "[%s] %s", static_cast<SDL_LogPriority>(level), "[%s] %s",
getLevelString(level), msg.c_str()); getLevelString(level), msg.c_str());
#endif
} }
/** /**
@ -210,9 +226,13 @@ public:
static void log(LogLevel level, const char *msg) { static void log(LogLevel level, const char *msg) {
if (static_cast<int>(level) < static_cast<int>(level_)) if (static_cast<int>(level) < static_cast<int>(level_))
return; return;
#ifdef TEST_BUILD
printf("[%s] %s\n", getLevelString(level), msg);
#else
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
static_cast<SDL_LogPriority>(level), "[%s] %s", static_cast<SDL_LogPriority>(level), "[%s] %s",
getLevelString(level), msg); getLevelString(level), msg);
#endif
} }
private: private:

View File

@ -27,15 +27,34 @@ void ListenerContainer::addListener(ListenerBase *listener) {
return; return;
if (broadcasting_ > 0) { if (broadcasting_ > 0) {
listener->entry_->next = listenersToAdd_; // 广播期间,添加到待添加列表(保持顺序,使用尾插)
listener->entry_->next = nullptr;
if (!listenersToAdd_) {
listenersToAdd_ = listener->entry_; listenersToAdd_ = listener->entry_;
} else { } else {
listener->entry_->next = listenerList_; // 找到尾部
listener->entry_->prev = nullptr; ListenerEntry* tail = listenersToAdd_;
if (listenerList_) { while (tail->next) {
listenerList_->prev = listener->entry_; tail = tail->next;
} }
tail->next = listener->entry_;
}
} else {
// 非广播期间,添加到主列表尾部(保持顺序)
listener->entry_->next = nullptr;
listener->entry_->prev = nullptr;
if (!listenerList_) {
listenerList_ = listener->entry_; listenerList_ = listener->entry_;
} else {
// 找到尾部
ListenerEntry* tail = listenerList_;
while (tail->next) {
tail = tail->next;
}
tail->next = listener->entry_;
listener->entry_->prev = tail;
}
} }
} }

View File

@ -21,8 +21,13 @@ void ModuleRegistry::registerModule(IModule* module) {
} }
const char* name = module->name(); const char* name = module->name();
if (!name || moduleMap_.find(name) != moduleMap_.end()) { if (!name) {
return; // 已存在或名称为空 return; // 名称为空
}
// 如果已存在同名模块,先注销旧的
if (moduleMap_.find(name) != moduleMap_.end()) {
unregisterModule(name);
} }
modules_.push_back(module); modules_.push_back(module);

View File

@ -102,10 +102,15 @@ void PluginLoader::registerPlugin(IPlugin* plugin) {
} }
const char* name = plugin->getInfo().name.c_str(); const char* name = plugin->getInfo().name.c_str();
if (!name || hasPlugin(name)) { if (!name) {
return; return;
} }
// 如果已存在同名插件,先卸载旧的
if (hasPlugin(name)) {
unloadPlugin(name);
}
PluginEntry entry; PluginEntry entry;
entry.plugin = plugin; entry.plugin = plugin;
entry.handle = nullptr; entry.handle = nullptr;
@ -186,6 +191,13 @@ bool PluginLoader::initAll() {
return true; return true;
} }
// 检查所有插件的依赖是否满足
for (const auto& pair : plugins_) {
if (pair.second.plugin && !checkDependencies(pair.second.plugin->getDependencies())) {
return false;
}
}
// 按依赖顺序排序 // 按依赖顺序排序
sortPluginsByDependencies(); sortPluginsByDependencies();

139
tests/README.md Normal file
View File

@ -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 <your_module.h>
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功能

7
tests/main.cpp Normal file
View File

@ -0,0 +1,7 @@
#include <gtest/gtest.h>
// GoogleTest 主入口
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

408
tests/test_context.cpp Normal file
View File

@ -0,0 +1,408 @@
#include <gtest/gtest.h>
#include <context/context.h>
#include <module/module_registry.h>
#include <module/timer_module.h>
#include <plugin/plugin_loader.h>
#include <event/event_bus.h>
#include <event/events.h>
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<events::OnUpdate> updateListener;
updateListener.bind([&](float dt) {
updateCount++;
receivedDt = dt;
});
Listener<events::OnLateUpdate> 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<events::OnInit> 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<events::OnShutdown> 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<std::string> eventLog;
auto context = Context::create();
// 注册事件监听器
Listener<events::OnInit> initListener;
initListener.bind([&]() {
eventLog.push_back("Init");
});
Listener<events::OnUpdate> updateListener;
updateListener.bind([&](float dt) {
if (eventLog.empty() || eventLog.back() != "Update") {
eventLog.push_back("Update");
}
});
Listener<events::OnLateUpdate> lateUpdateListener;
lateUpdateListener.bind([&](float dt) {
if (eventLog.empty() || eventLog.back() != "LateUpdate") {
eventLog.push_back("LateUpdate");
}
});
Listener<events::OnShutdown> 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<events::OnUpdate> 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();
}

534
tests/test_event_bus.cpp Normal file
View File

@ -0,0 +1,534 @@
#include <gtest/gtest.h>
#include <event/event_bus.h>
#include <event/events.h>
using namespace extra2d;
using extra2d::event::broadcast;
using extra2d::event::Listener;
// ============================================================================
// 基础事件测试
// ============================================================================
TEST(EventBus, BasicBroadcast) {
int received = 0;
{
Listener<events::OnInit> listener;
listener.bind([&received]() {
received++;
});
// 广播事件
broadcast<events::OnInit>();
EXPECT_EQ(1, received);
// 再次广播
broadcast<events::OnInit>();
EXPECT_EQ(2, received);
}
// 监听器销毁后不应再接收
broadcast<events::OnInit>();
EXPECT_EQ(2, received);
}
TEST(EventBus, MultipleListeners) {
int count1 = 0, count2 = 0, count3 = 0;
Listener<events::OnUpdate> listener1;
listener1.bind([&count1](float dt) {
count1++;
});
Listener<events::OnUpdate> listener2;
listener2.bind([&count2](float dt) {
count2++;
});
Listener<events::OnUpdate> listener3;
listener3.bind([&count3](float dt) {
count3++;
});
broadcast<events::OnUpdate>(0.016f);
EXPECT_EQ(1, count1);
EXPECT_EQ(1, count2);
EXPECT_EQ(1, count3);
}
TEST(EventBus, ListenerEnableDisable) {
int count = 0;
Listener<events::OnInit> listener;
listener.bind([&count]() {
count++;
});
// 默认启用
broadcast<events::OnInit>();
EXPECT_EQ(1, count);
// 禁用
listener.disable();
EXPECT_FALSE(listener.isEnabled());
broadcast<events::OnInit>();
EXPECT_EQ(1, count); // 不应增加
// 重新启用
listener.enable();
EXPECT_TRUE(listener.isEnabled());
broadcast<events::OnInit>();
EXPECT_EQ(2, count);
}
TEST(EventBus, ListenerReset) {
int count = 0;
Listener<events::OnInit> listener;
listener.bind([&count]() {
count++;
});
broadcast<events::OnInit>();
EXPECT_EQ(1, count);
// 重置回调
listener.reset();
broadcast<events::OnInit>();
EXPECT_EQ(1, count); // 不应增加
}
// ============================================================================
// 带参数事件测试
// ============================================================================
TEST(EventBus, EventWithParameters) {
float receivedDt = 0.0f;
Listener<events::OnUpdate> listener;
listener.bind([&receivedDt](float dt) {
receivedDt = dt;
});
broadcast<events::OnUpdate>(0.016f);
EXPECT_NEAR(0.016f, receivedDt, 0.0001f);
broadcast<events::OnUpdate>(0.033f);
EXPECT_NEAR(0.033f, receivedDt, 0.0001f);
}
TEST(EventBus, EventWithMultipleParameters) {
int receivedWidth = 0, receivedHeight = 0;
Listener<events::OnResize> listener;
listener.bind([&receivedWidth, &receivedHeight](int32 width, int32 height) {
receivedWidth = width;
receivedHeight = height;
});
broadcast<events::OnResize>(1920, 1080);
EXPECT_EQ(1920, receivedWidth);
EXPECT_EQ(1080, receivedHeight);
broadcast<events::OnResize>(1280, 720);
EXPECT_EQ(1280, receivedWidth);
EXPECT_EQ(720, receivedHeight);
}
// ============================================================================
// 不同事件类型测试
// ============================================================================
TEST(EventBus, DifferentEventTypes) {
int initCount = 0;
int updateCount = 0;
int shutdownCount = 0;
Listener<events::OnInit> listener1;
listener1.bind([&initCount]() {
initCount++;
});
Listener<events::OnUpdate> listener2;
listener2.bind([&updateCount](float dt) {
updateCount++;
});
Listener<events::OnShutdown> listener3;
listener3.bind([&shutdownCount]() {
shutdownCount++;
});
// 广播不同类型的事件
broadcast<events::OnInit>();
EXPECT_EQ(1, initCount);
EXPECT_EQ(0, updateCount);
EXPECT_EQ(0, shutdownCount);
broadcast<events::OnUpdate>(0.016f);
EXPECT_EQ(1, initCount);
EXPECT_EQ(1, updateCount);
EXPECT_EQ(0, shutdownCount);
broadcast<events::OnShutdown>();
EXPECT_EQ(1, initCount);
EXPECT_EQ(1, updateCount);
EXPECT_EQ(1, shutdownCount);
}
// ============================================================================
// 监听器生命周期测试
// ============================================================================
TEST(EventBus, ListenerAutoUnregister) {
int count = 0;
{
Listener<events::OnInit> listener;
listener.bind([&count]() {
count++;
});
broadcast<events::OnInit>();
EXPECT_EQ(1, count);
} // listener 在这里销毁
// 再次广播,不应触发
broadcast<events::OnInit>();
EXPECT_EQ(1, count);
}
TEST(EventBus, ListenerOrder) {
std::vector<int> order;
Listener<events::OnInit> listener1;
listener1.bind([&order]() {
order.push_back(1);
});
Listener<events::OnInit> listener2;
listener2.bind([&order]() {
order.push_back(2);
});
Listener<events::OnInit> listener3;
listener3.bind([&order]() {
order.push_back(3);
});
broadcast<events::OnInit>();
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<events::OnInit>* listener2 = nullptr;
Listener<events::OnInit> listener1;
listener1.bind([&]() {
count1++;
// 在回调中创建新监听器
if (!listener2) {
listener2 = new Listener<events::OnInit>();
listener2->bind([&count2]() {
count2++;
});
}
});
broadcast<events::OnInit>();
EXPECT_EQ(1, count1);
// 新监听器在当前广播中不应被触发
EXPECT_EQ(0, count2);
// 下一次广播,新监听器应该被触发
broadcast<events::OnInit>();
EXPECT_EQ(2, count1);
EXPECT_EQ(1, count2);
delete listener2;
}
TEST(EventBus, RemoveListenerDuringBroadcast) {
int count1 = 0, count2 = 0, count3 = 0;
Listener<events::OnInit> listener1;
listener1.bind([&]() {
count1++;
});
{
Listener<events::OnInit> listener2;
listener2.bind([&]() {
count2++;
});
Listener<events::OnInit> listener3;
listener3.bind([&]() {
count3++;
});
broadcast<events::OnInit>();
EXPECT_EQ(1, count1);
EXPECT_EQ(1, count2);
EXPECT_EQ(1, count3);
} // listener2 和 listener3 在这里销毁
// 再次广播
broadcast<events::OnInit>();
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<events::OnKeyDown> keyDownListener;
keyDownListener.bind([&keyDownCode](int32 key) {
keyDownCode = key;
});
Listener<events::OnKeyUp> keyUpListener;
keyUpListener.bind([&keyUpCode](int32 key) {
keyUpCode = key;
});
Listener<events::OnMouseDown> mouseDownListener;
mouseDownListener.bind([&mouseButton, &mouseX, &mouseY](int32 button, int32 x, int32 y) {
mouseButton = button;
mouseX = x;
mouseY = y;
});
// 模拟输入事件
broadcast<events::OnKeyDown>(65); // 'A' key
EXPECT_EQ(65, keyDownCode);
broadcast<events::OnKeyUp>(65);
EXPECT_EQ(65, keyUpCode);
broadcast<events::OnMouseDown>(0, 100, 200); // 左键点击
EXPECT_EQ(0, mouseButton);
EXPECT_EQ(100, mouseX);
EXPECT_EQ(200, mouseY);
}
TEST(EventBus, MouseMoveEvent) {
int receivedX = -1, receivedY = -1;
Listener<events::OnMouseMove> listener;
listener.bind([&receivedX, &receivedY](int32 x, int32 y) {
receivedX = x;
receivedY = y;
});
broadcast<events::OnMouseMove>(500, 300);
EXPECT_EQ(500, receivedX);
EXPECT_EQ(300, receivedY);
}
TEST(EventBus, MouseWheelEvent) {
int receivedDelta = 0;
Listener<events::OnMouseWheel> listener;
listener.bind([&receivedDelta](int32 delta) {
receivedDelta = delta;
});
broadcast<events::OnMouseWheel>(120);
EXPECT_EQ(120, receivedDelta);
broadcast<events::OnMouseWheel>(-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<events::OnFocus> focusListener;
focusListener.bind([&focusReceived]() {
focusReceived = true;
});
Listener<events::OnBlur> blurListener;
blurListener.bind([&blurReceived]() {
blurReceived = true;
});
Listener<events::OnShow> showListener;
showListener.bind([&showReceived]() {
showReceived = true;
});
Listener<events::OnHide> hideListener;
hideListener.bind([&hideReceived]() {
hideReceived = true;
});
Listener<events::OnClose> closeListener;
closeListener.bind([&closeReceived]() {
closeReceived = true;
});
Listener<events::OnResize> resizeListener;
resizeListener.bind([&resizeWidth, &resizeHeight](int32 width, int32 height) {
resizeWidth = width;
resizeHeight = height;
});
broadcast<events::OnFocus>();
EXPECT_TRUE(focusReceived);
broadcast<events::OnBlur>();
EXPECT_TRUE(blurReceived);
broadcast<events::OnShow>();
EXPECT_TRUE(showReceived);
broadcast<events::OnHide>();
EXPECT_TRUE(hideReceived);
broadcast<events::OnClose>();
EXPECT_TRUE(closeReceived);
broadcast<events::OnResize>(800, 600);
EXPECT_EQ(800, resizeWidth);
EXPECT_EQ(600, resizeHeight);
}
// ============================================================================
// 触摸事件测试
// ============================================================================
TEST(EventBus, TouchEvents) {
int touchId = -1;
int touchX = -1, touchY = -1;
Listener<events::OnTouchBegin> touchBeginListener;
touchBeginListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) {
touchId = id;
touchX = x;
touchY = y;
});
broadcast<events::OnTouchBegin>(0, 100, 200);
EXPECT_EQ(0, touchId);
EXPECT_EQ(100, touchX);
EXPECT_EQ(200, touchY);
Listener<events::OnTouchMove> touchMoveListener;
touchMoveListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) {
touchId = id;
touchX = x;
touchY = y;
});
broadcast<events::OnTouchMove>(0, 150, 250);
EXPECT_EQ(0, touchId);
EXPECT_EQ(150, touchX);
EXPECT_EQ(250, touchY);
Listener<events::OnTouchEnd> touchEndListener;
touchEndListener.bind([&touchId, &touchX, &touchY](int32 id, int32 x, int32 y) {
touchId = id;
touchX = x;
touchY = y;
});
broadcast<events::OnTouchEnd>(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<events::OnGamepadConnect> connectListener;
connectListener.bind([&connectedDeviceId](int32 deviceId) {
connectedDeviceId = deviceId;
});
Listener<events::OnGamepadDisconnect> disconnectListener;
disconnectListener.bind([&disconnectedDeviceId](int32 deviceId) {
disconnectedDeviceId = deviceId;
});
broadcast<events::OnGamepadConnect>(0);
EXPECT_EQ(0, connectedDeviceId);
broadcast<events::OnGamepadDisconnect>(0);
EXPECT_EQ(0, disconnectedDeviceId);
}
// ============================================================================
// 性能测试
// ============================================================================
TEST(EventBus, HighFrequencyBroadcast) {
const int iterations = 10000;
int count = 0;
Listener<events::OnUpdate> listener;
listener.bind([&count](float dt) {
count++;
});
for (int i = 0; i < iterations; i++) {
broadcast<events::OnUpdate>(0.016f);
}
EXPECT_EQ(iterations, count);
}
TEST(EventBus, ManyListeners) {
const int listenerCount = 100;
int totalCount = 0;
std::vector<std::unique_ptr<Listener<events::OnInit>>> listeners;
for (int i = 0; i < listenerCount; i++) {
auto listener = std::make_unique<Listener<events::OnInit>>();
listener->bind([&totalCount]() {
totalCount++;
});
listeners.push_back(std::move(listener));
}
broadcast<events::OnInit>();
EXPECT_EQ(listenerCount, totalCount);
}

View File

@ -0,0 +1,322 @@
#include <gtest/gtest.h>
#include <module/module_registry.h>
#include <module/imodule.h>
#include <types/const/priority.h>
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<std::string> initOrder;
std::vector<std::string> shutdownOrder;
class OrderTrackingModule : public IModule {
public:
OrderTrackingModule(const char* name, int priority,
std::vector<std::string>& initVec,
std::vector<std::string>& 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<std::string>& initOrder_;
std::vector<std::string>& 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);
}

View File

@ -0,0 +1,413 @@
#include <gtest/gtest.h>
#include <plugin/plugin_loader.h>
#include <plugin/iplugin.h>
#include <algorithm>
using namespace extra2d;
// ============================================================================
// 测试用插件类
// ============================================================================
class TestPlugin : public IPlugin {
public:
TestPlugin(const char* name, const char* version = "1.0.0",
const std::vector<std::string>& 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<std::string> getDependencies() const override {
return dependencies_;
}
std::string name_;
std::string version_;
std::vector<std::string> 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 依赖 PluginBPluginB 依赖 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 依赖 BB 依赖 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<std::string> loadOrder;
std::vector<std::string> unloadOrder;
class OrderTrackingPlugin : public IPlugin {
public:
OrderTrackingPlugin(const char* name,
const std::vector<std::string>& deps,
std::vector<std::string>& loadVec,
std::vector<std::string>& 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<std::string>& loadOrder_;
std::vector<std::string>& unloadOrder_;
};
// C 依赖 BB 依赖 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();
}

405
tests/test_timer_module.cpp Normal file
View File

@ -0,0 +1,405 @@
#include <gtest/gtest.h>
#include <module/timer_module.h>
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();
}

59
tests/xmake.lua Normal file
View File

@ -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

View File

@ -32,6 +32,12 @@ option("debug_logs")
set_description("Enable debug logging") set_description("Enable debug logging")
option_end() 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() define_extra2d_engine()
-- 示例程序目标(作为子项目) -- -- 示例程序目标(作为子项目)
if is_config("examples","true") then -- if is_config("examples","true") then
includes("examples/hello_world", {rootdir = "examples/hello_world"}) -- includes("examples/hello_world", {rootdir = "examples/hello_world"})
includes("examples/collision_demo", {rootdir = "examples/collision_demo"}) -- includes("examples/collision_demo", {rootdir = "examples/collision_demo"})
-- end
-- 测试套件目标(作为子项目)
if is_config("tests","true") then
includes("tests")
end end
-- ============================================== -- ==============================================
@ -102,4 +113,5 @@ print("Platform: " .. target_plat)
print("Architecture: " .. (get_config("arch") or "auto")) print("Architecture: " .. (get_config("arch") or "auto"))
print("Mode: " .. (is_mode("debug") and "debug" or "release")) print("Mode: " .. (is_mode("debug") and "debug" or "release"))
print("Examples: " .. (has_config("examples") and "enabled" or "disabled")) print("Examples: " .. (has_config("examples") and "enabled" or "disabled"))
print("Tests: " .. (has_config("tests") and "enabled" or "disabled"))
print("========================================") print("========================================")