414 lines
12 KiB
C++
414 lines
12 KiB
C++
|
|
#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();
|
|||
|
|
}
|