feat(tests): 添加完整的测试套件框架和核心模块测试
refactor(module): 改进模块注册表同名模块处理逻辑 refactor(plugin): 优化插件加载器依赖检查机制 refactor(event): 重构事件总线监听器添加顺序逻辑 build: 添加测试构建选项和配置 docs: 添加测试套件README文档
This commit is contained in:
parent
e68ce87638
commit
418d2c8f92
|
|
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)功能
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// GoogleTest 主入口
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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 依赖 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<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 依赖 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();
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
20
xmake.lua
20
xmake.lua
|
|
@ -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("========================================")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue