2026-01-22 14:56:51 -05:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include "plugin_manager.h"
|
|
|
|
|
#include "logos_core_internal.h"
|
2026-02-03 11:29:38 -05:00
|
|
|
#include "logos_core.h"
|
2026-01-22 14:56:51 -05:00
|
|
|
#include "logos_mode.h"
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QTemporaryDir>
|
|
|
|
|
#include <QFile>
|
2026-02-03 11:29:38 -05:00
|
|
|
#include <QJsonArray>
|
2026-01-22 14:56:51 -05:00
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
// Test fixture for plugin manager tests
|
|
|
|
|
class PluginManagerTest : public ::testing::Test {
|
|
|
|
|
protected:
|
|
|
|
|
void SetUp() override {
|
2026-01-22 15:08:47 -05:00
|
|
|
// Clear global state before each test using public API
|
|
|
|
|
PluginManager::clearState();
|
2026-01-22 14:56:51 -05:00
|
|
|
|
|
|
|
|
// Set to Local mode by default (most testable functions require it)
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Local);
|
|
|
|
|
|
|
|
|
|
// Ensure registry host is null at start
|
|
|
|
|
if (g_registry_host) {
|
|
|
|
|
delete g_registry_host;
|
|
|
|
|
g_registry_host = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TearDown() override {
|
|
|
|
|
// Clean up after each test
|
|
|
|
|
|
|
|
|
|
// Clean up registry host if it was created
|
|
|
|
|
if (g_registry_host) {
|
|
|
|
|
delete g_registry_host;
|
|
|
|
|
g_registry_host = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 15:08:47 -05:00
|
|
|
// Clear plugin state using public API
|
|
|
|
|
PluginManager::clearState();
|
2026-01-22 14:56:51 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Plugin Query Functions Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that getLoadedPlugins() returns an empty list when no plugins are loaded
|
|
|
|
|
TEST_F(PluginManagerTest, GetLoadedPlugins_ReturnsEmptyList) {
|
|
|
|
|
QStringList loaded = PluginManager::getLoadedPlugins();
|
|
|
|
|
EXPECT_TRUE(loaded.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getLoadedPlugins() returns the correct list of loaded plugin names
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test that directly manipulates internal state
|
2026-01-22 14:56:51 -05:00
|
|
|
TEST_F(PluginManagerTest, GetLoadedPlugins_ReturnsCorrectList) {
|
|
|
|
|
// Add some plugins to the global list
|
|
|
|
|
g_loaded_plugins.append("plugin1");
|
|
|
|
|
g_loaded_plugins.append("plugin2");
|
|
|
|
|
g_loaded_plugins.append("plugin3");
|
|
|
|
|
|
|
|
|
|
QStringList loaded = PluginManager::getLoadedPlugins();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(loaded.size(), 3);
|
|
|
|
|
EXPECT_EQ(loaded[0].toStdString(), "plugin1");
|
|
|
|
|
EXPECT_EQ(loaded[1].toStdString(), "plugin2");
|
|
|
|
|
EXPECT_EQ(loaded[2].toStdString(), "plugin3");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getKnownPlugins() returns an empty hash when no plugins are known
|
|
|
|
|
TEST_F(PluginManagerTest, GetKnownPlugins_ReturnsEmptyHash) {
|
|
|
|
|
QHash<QString, QString> known = PluginManager::getKnownPlugins();
|
|
|
|
|
EXPECT_TRUE(known.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getKnownPlugins() returns the correct hash of plugin names to paths
|
|
|
|
|
TEST_F(PluginManagerTest, GetKnownPlugins_ReturnsCorrectHash) {
|
2026-01-22 15:08:47 -05:00
|
|
|
// Add some plugins using the public API
|
|
|
|
|
PluginManager::addKnownPlugin("plugin1", "/path/to/plugin1.dylib");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin2", "/path/to/plugin2.dylib");
|
2026-01-22 14:56:51 -05:00
|
|
|
|
|
|
|
|
QHash<QString, QString> known = PluginManager::getKnownPlugins();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(known.size(), 2);
|
|
|
|
|
EXPECT_EQ(known.value("plugin1").toStdString(), "/path/to/plugin1.dylib");
|
|
|
|
|
EXPECT_EQ(known.value("plugin2").toStdString(), "/path/to/plugin2.dylib");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that isPluginLoaded() returns false for plugins that are not loaded
|
|
|
|
|
TEST_F(PluginManagerTest, IsPluginLoaded_ReturnsFalseForUnloaded) {
|
|
|
|
|
EXPECT_FALSE(PluginManager::isPluginLoaded("nonexistent_plugin"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that isPluginLoaded() returns true for plugins that are currently loaded
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test that directly manipulates internal state
|
2026-01-22 14:56:51 -05:00
|
|
|
TEST_F(PluginManagerTest, IsPluginLoaded_ReturnsTrueForLoaded) {
|
|
|
|
|
g_loaded_plugins.append("test_plugin");
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(PluginManager::isPluginLoaded("test_plugin"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that isPluginKnown() returns false for plugins that have not been discovered
|
|
|
|
|
TEST_F(PluginManagerTest, IsPluginKnown_ReturnsFalseForUnknown) {
|
|
|
|
|
EXPECT_FALSE(PluginManager::isPluginKnown("nonexistent_plugin"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that isPluginKnown() returns true for plugins that have been discovered
|
|
|
|
|
TEST_F(PluginManagerTest, IsPluginKnown_ReturnsTrueForKnown) {
|
2026-01-22 15:08:47 -05:00
|
|
|
PluginManager::addKnownPlugin("test_plugin", "/path/to/plugin");
|
2026-01-22 14:56:51 -05:00
|
|
|
|
|
|
|
|
EXPECT_TRUE(PluginManager::isPluginKnown("test_plugin"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// C String Array Functions Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that getLoadedPluginsCStr() returns a null-terminated array with just NULL when no plugins are loaded
|
|
|
|
|
TEST_F(PluginManagerTest, GetLoadedPluginsCStr_ReturnsNullTerminatedArrayWhenEmpty) {
|
|
|
|
|
char** result = PluginManager::getLoadedPluginsCStr();
|
|
|
|
|
|
|
|
|
|
ASSERT_NE(result, nullptr);
|
|
|
|
|
EXPECT_EQ(result[0], nullptr);
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getLoadedPluginsCStr() returns a properly allocated null-terminated C string array
|
|
|
|
|
// containing all loaded plugin names for C API compatibility
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test that directly manipulates internal state
|
2026-01-22 14:56:51 -05:00
|
|
|
TEST_F(PluginManagerTest, GetLoadedPluginsCStr_ReturnsCorrectArray) {
|
|
|
|
|
// Add plugins to the global list
|
|
|
|
|
g_loaded_plugins.append("plugin1");
|
|
|
|
|
g_loaded_plugins.append("plugin2");
|
|
|
|
|
|
|
|
|
|
char** result = PluginManager::getLoadedPluginsCStr();
|
|
|
|
|
|
|
|
|
|
ASSERT_NE(result, nullptr);
|
|
|
|
|
ASSERT_NE(result[0], nullptr);
|
|
|
|
|
ASSERT_NE(result[1], nullptr);
|
|
|
|
|
EXPECT_EQ(result[2], nullptr); // Null terminator
|
|
|
|
|
|
|
|
|
|
EXPECT_STREQ(result[0], "plugin1");
|
|
|
|
|
EXPECT_STREQ(result[1], "plugin2");
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
delete[] result[0];
|
|
|
|
|
delete[] result[1];
|
|
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getKnownPluginsCStr() returns a null-terminated array with just NULL when no plugins are known
|
|
|
|
|
TEST_F(PluginManagerTest, GetKnownPluginsCStr_ReturnsNullTerminatedArrayWhenEmpty) {
|
|
|
|
|
char** result = PluginManager::getKnownPluginsCStr();
|
|
|
|
|
|
|
|
|
|
ASSERT_NE(result, nullptr);
|
|
|
|
|
EXPECT_EQ(result[0], nullptr);
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that getKnownPluginsCStr() returns a properly allocated null-terminated C string array
|
|
|
|
|
// containing all known plugin names for C API compatibility
|
|
|
|
|
TEST_F(PluginManagerTest, GetKnownPluginsCStr_ReturnsCorrectArray) {
|
2026-01-22 15:08:47 -05:00
|
|
|
// Add plugins using the public API
|
|
|
|
|
PluginManager::addKnownPlugin("plugin1", "/path/to/plugin1");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin2", "/path/to/plugin2");
|
2026-01-22 14:56:51 -05:00
|
|
|
|
|
|
|
|
char** result = PluginManager::getKnownPluginsCStr();
|
|
|
|
|
|
|
|
|
|
ASSERT_NE(result, nullptr);
|
|
|
|
|
ASSERT_NE(result[0], nullptr);
|
|
|
|
|
ASSERT_NE(result[1], nullptr);
|
|
|
|
|
EXPECT_EQ(result[2], nullptr); // Null terminator
|
|
|
|
|
|
|
|
|
|
// Note: QHash order is not guaranteed, so we check that both plugins are present
|
|
|
|
|
QStringList plugins;
|
|
|
|
|
plugins.append(QString::fromUtf8(result[0]));
|
|
|
|
|
plugins.append(QString::fromUtf8(result[1]));
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(plugins.contains("plugin1"));
|
|
|
|
|
EXPECT_TRUE(plugins.contains("plugin2"));
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
delete[] result[0];
|
|
|
|
|
delete[] result[1];
|
|
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// findPlugins Function Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that findPlugins() returns an empty list when the directory does not exist
|
|
|
|
|
TEST_F(PluginManagerTest, FindPlugins_ReturnsEmptyForNonExistentDir) {
|
|
|
|
|
QStringList plugins = PluginManager::findPlugins("/nonexistent/directory");
|
|
|
|
|
EXPECT_TRUE(plugins.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that findPlugins() returns an empty list when the directory exists but contains no plugins
|
|
|
|
|
TEST_F(PluginManagerTest, FindPlugins_ReturnsEmptyForEmptyDir) {
|
|
|
|
|
QTemporaryDir tempDir;
|
|
|
|
|
ASSERT_TRUE(tempDir.isValid());
|
|
|
|
|
|
|
|
|
|
QStringList plugins = PluginManager::findPlugins(tempDir.path());
|
|
|
|
|
EXPECT_TRUE(plugins.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that findPlugins() correctly filters plugin files by platform-specific extensions
|
|
|
|
|
// (.dylib on macOS, .so on Linux, .dll on Windows) and ignores other file types
|
|
|
|
|
TEST_F(PluginManagerTest, FindPlugins_FiltersByPlatformExtension) {
|
|
|
|
|
QTemporaryDir tempDir;
|
|
|
|
|
ASSERT_TRUE(tempDir.isValid());
|
|
|
|
|
|
|
|
|
|
// Create test files with various extensions
|
|
|
|
|
QString correctExt;
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
correctExt = ".dll";
|
|
|
|
|
#elif defined(Q_OS_MAC)
|
|
|
|
|
correctExt = ".dylib";
|
|
|
|
|
#else
|
|
|
|
|
correctExt = ".so";
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Create a file with the correct extension
|
|
|
|
|
QFile correctFile(tempDir.filePath("plugin" + correctExt));
|
|
|
|
|
ASSERT_TRUE(correctFile.open(QIODevice::WriteOnly));
|
|
|
|
|
correctFile.close();
|
|
|
|
|
|
|
|
|
|
// Create files with wrong extensions
|
|
|
|
|
QFile txtFile(tempDir.filePath("plugin.txt"));
|
|
|
|
|
ASSERT_TRUE(txtFile.open(QIODevice::WriteOnly));
|
|
|
|
|
txtFile.close();
|
|
|
|
|
|
|
|
|
|
QFile noExtFile(tempDir.filePath("plugin"));
|
|
|
|
|
ASSERT_TRUE(noExtFile.open(QIODevice::WriteOnly));
|
|
|
|
|
noExtFile.close();
|
|
|
|
|
|
|
|
|
|
QStringList plugins = PluginManager::findPlugins(tempDir.path());
|
|
|
|
|
|
|
|
|
|
// Should only find the file with the correct extension
|
|
|
|
|
ASSERT_EQ(plugins.size(), 1);
|
|
|
|
|
EXPECT_TRUE(plugins[0].endsWith(correctExt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// loadPlugin Error Cases Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that loadPlugin() returns false when attempting to load a plugin that is not in the known plugins list
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPlugin_ReturnsFalseForUnknownPlugin) {
|
|
|
|
|
bool result = PluginManager::loadPlugin("nonexistent_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that loadPlugin() returns false when attempting to load a plugin that is already loaded in Local mode
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPlugin_ReturnsFalseForAlreadyLoadedLocal) {
|
|
|
|
|
// Ensure we're in Local mode (default, but being explicit)
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Local);
|
|
|
|
|
|
|
|
|
|
// Simulate a loaded plugin in Local mode
|
2026-01-22 15:08:47 -05:00
|
|
|
PluginManager::addKnownPlugin("test_plugin", "/path/to/plugin");
|
2026-01-22 14:56:51 -05:00
|
|
|
g_loaded_plugins.append("test_plugin");
|
|
|
|
|
|
|
|
|
|
// Create a dummy LogosAPI to simulate loaded plugin
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test verifying internal state checking
|
2026-01-22 14:56:51 -05:00
|
|
|
g_local_plugin_apis.insert("test_plugin", nullptr);
|
|
|
|
|
|
|
|
|
|
bool result = PluginManager::loadPlugin("test_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef Q_OS_IOS
|
|
|
|
|
// Verifies that loadPlugin() returns false when attempting to load a plugin that is already running
|
|
|
|
|
// in a separate process in Remote mode
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPlugin_ReturnsFalseForAlreadyLoadedRemote) {
|
|
|
|
|
// Set to Remote mode explicitly
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Remote);
|
|
|
|
|
|
|
|
|
|
// Simulate a loaded plugin in Remote mode
|
2026-01-22 15:08:47 -05:00
|
|
|
PluginManager::addKnownPlugin("test_plugin", "/path/to/plugin");
|
2026-01-22 14:56:51 -05:00
|
|
|
g_loaded_plugins.append("test_plugin");
|
|
|
|
|
|
|
|
|
|
// Create a dummy process to simulate loaded plugin
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test verifying internal state checking
|
2026-01-22 14:56:51 -05:00
|
|
|
QProcess* dummyProcess = new QProcess();
|
|
|
|
|
g_plugin_processes.insert("test_plugin", dummyProcess);
|
|
|
|
|
|
|
|
|
|
bool result = PluginManager::loadPlugin("test_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// unloadPlugin Error Cases Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_IOS
|
|
|
|
|
// Verifies that unloadPlugin() is not supported on iOS (which doesn't support process-based plugins)
|
|
|
|
|
TEST_F(PluginManagerTest, UnloadPlugin_NotSupportedOnIOS) {
|
|
|
|
|
bool result = PluginManager::unloadPlugin("any_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
// Verifies that unloadPlugin() returns false when attempting to unload a plugin that is not loaded
|
|
|
|
|
TEST_F(PluginManagerTest, UnloadPlugin_ReturnsFalseForNotLoaded) {
|
|
|
|
|
bool result = PluginManager::unloadPlugin("nonexistent_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that unloadPlugin() returns false when a plugin is marked as loaded but has no associated process
|
|
|
|
|
TEST_F(PluginManagerTest, UnloadPlugin_ReturnsFalseForNoProcess) {
|
|
|
|
|
// Add plugin to loaded list but not to processes
|
2026-01-22 15:08:47 -05:00
|
|
|
// Note: This is an implementation test verifying internal state consistency
|
2026-01-22 14:56:51 -05:00
|
|
|
g_loaded_plugins.append("test_plugin");
|
|
|
|
|
|
|
|
|
|
bool result = PluginManager::unloadPlugin("test_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Mode-Dependent Functions Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that loadStaticPlugins() returns 0 when no static plugins are registered
|
|
|
|
|
// (requires Local mode for static plugin loading)
|
|
|
|
|
TEST_F(PluginManagerTest, LoadStaticPlugins_ReturnsZeroWhenNoPlugins) {
|
|
|
|
|
// Set to Local mode (required for loadStaticPlugins)
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Local);
|
|
|
|
|
|
|
|
|
|
// Should return 0 when no static plugins are registered
|
|
|
|
|
int result = PluginManager::loadStaticPlugins();
|
|
|
|
|
EXPECT_EQ(result, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that registerPluginInstance() returns false when given a null plugin instance pointer
|
|
|
|
|
// (requires Local mode for direct plugin registration)
|
|
|
|
|
TEST_F(PluginManagerTest, RegisterPluginInstance_ReturnsFalseForNullInstance) {
|
|
|
|
|
// Set to Local mode (required for registerPluginInstance)
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Local);
|
|
|
|
|
|
|
|
|
|
bool result = PluginManager::registerPluginInstance("test_plugin", nullptr);
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that registerPluginByName() returns false when attempting to register a static plugin
|
|
|
|
|
// that doesn't exist in the static plugin instances list (requires Local mode)
|
|
|
|
|
TEST_F(PluginManagerTest, RegisterPluginByName_ReturnsFalseForUnfoundPlugin) {
|
|
|
|
|
// Set to Local mode (required for registerPluginByName)
|
|
|
|
|
LogosModeConfig::setMode(LogosMode::Local);
|
|
|
|
|
|
|
|
|
|
// Try to register a non-existent plugin
|
|
|
|
|
bool result = PluginManager::registerPluginByName("nonexistent_plugin");
|
|
|
|
|
EXPECT_FALSE(result);
|
|
|
|
|
}
|
2026-02-03 11:29:38 -05:00
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// resolveDependencies Function Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that resolveDependencies() returns an empty list when given an empty list
|
|
|
|
|
TEST_F(PluginManagerTest, ResolveDependencies_ReturnsEmptyForEmptyInput) {
|
|
|
|
|
QStringList result = PluginManager::resolveDependencies(QStringList());
|
|
|
|
|
EXPECT_TRUE(result.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that resolveDependencies() returns an empty list when the plugin is not known
|
|
|
|
|
TEST_F(PluginManagerTest, ResolveDependencies_ReturnsEmptyForUnknownPlugin) {
|
|
|
|
|
QStringList requested;
|
|
|
|
|
requested.append("unknown_plugin");
|
|
|
|
|
|
|
|
|
|
QStringList result = PluginManager::resolveDependencies(requested);
|
|
|
|
|
EXPECT_TRUE(result.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that resolveDependencies() returns the plugin itself when it has no dependencies
|
|
|
|
|
TEST_F(PluginManagerTest, ResolveDependencies_ReturnsSinglePluginWithNoDeps) {
|
|
|
|
|
// Add a known plugin with no dependencies
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_a", "/path/to/plugin_a");
|
|
|
|
|
QJsonObject metadata;
|
|
|
|
|
metadata["name"] = "plugin_a";
|
|
|
|
|
metadata["dependencies"] = QJsonArray();
|
|
|
|
|
g_plugin_metadata.insert("plugin_a", metadata);
|
|
|
|
|
|
|
|
|
|
QStringList requested;
|
|
|
|
|
requested.append("plugin_a");
|
|
|
|
|
|
|
|
|
|
QStringList result = PluginManager::resolveDependencies(requested);
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(result.size(), 1);
|
|
|
|
|
EXPECT_EQ(result[0].toStdString(), "plugin_a");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that resolveDependencies() returns dependencies in topological order (deps first)
|
|
|
|
|
TEST_F(PluginManagerTest, ResolveDependencies_ReturnsCorrectOrder) {
|
|
|
|
|
// Set up plugin_a which depends on plugin_b
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_a", "/path/to/plugin_a");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_b", "/path/to/plugin_b");
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataA;
|
|
|
|
|
metadataA["name"] = "plugin_a";
|
|
|
|
|
QJsonArray depsA;
|
|
|
|
|
depsA.append("plugin_b");
|
|
|
|
|
metadataA["dependencies"] = depsA;
|
|
|
|
|
g_plugin_metadata.insert("plugin_a", metadataA);
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataB;
|
|
|
|
|
metadataB["name"] = "plugin_b";
|
|
|
|
|
metadataB["dependencies"] = QJsonArray();
|
|
|
|
|
g_plugin_metadata.insert("plugin_b", metadataB);
|
|
|
|
|
|
|
|
|
|
QStringList requested;
|
|
|
|
|
requested.append("plugin_a");
|
|
|
|
|
|
|
|
|
|
QStringList result = PluginManager::resolveDependencies(requested);
|
|
|
|
|
|
|
|
|
|
// Should return both plugins, with plugin_b first (dependency order)
|
|
|
|
|
ASSERT_EQ(result.size(), 2);
|
|
|
|
|
EXPECT_EQ(result[0].toStdString(), "plugin_b"); // Dependency first
|
|
|
|
|
EXPECT_EQ(result[1].toStdString(), "plugin_a"); // Then the requested plugin
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that resolveDependencies() handles transitive dependencies correctly
|
|
|
|
|
TEST_F(PluginManagerTest, ResolveDependencies_HandlesTransitiveDeps) {
|
|
|
|
|
// Set up: plugin_a -> plugin_b -> plugin_c
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_a", "/path/to/plugin_a");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_b", "/path/to/plugin_b");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_c", "/path/to/plugin_c");
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataA;
|
|
|
|
|
metadataA["name"] = "plugin_a";
|
|
|
|
|
QJsonArray depsA;
|
|
|
|
|
depsA.append("plugin_b");
|
|
|
|
|
metadataA["dependencies"] = depsA;
|
|
|
|
|
g_plugin_metadata.insert("plugin_a", metadataA);
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataB;
|
|
|
|
|
metadataB["name"] = "plugin_b";
|
|
|
|
|
QJsonArray depsB;
|
|
|
|
|
depsB.append("plugin_c");
|
|
|
|
|
metadataB["dependencies"] = depsB;
|
|
|
|
|
g_plugin_metadata.insert("plugin_b", metadataB);
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataC;
|
|
|
|
|
metadataC["name"] = "plugin_c";
|
|
|
|
|
metadataC["dependencies"] = QJsonArray();
|
|
|
|
|
g_plugin_metadata.insert("plugin_c", metadataC);
|
|
|
|
|
|
|
|
|
|
QStringList requested;
|
|
|
|
|
requested.append("plugin_a");
|
|
|
|
|
|
|
|
|
|
QStringList result = PluginManager::resolveDependencies(requested);
|
|
|
|
|
|
|
|
|
|
// Should return all three plugins in correct order
|
|
|
|
|
ASSERT_EQ(result.size(), 3);
|
|
|
|
|
EXPECT_EQ(result[0].toStdString(), "plugin_c"); // Deepest dependency first
|
|
|
|
|
EXPECT_EQ(result[1].toStdString(), "plugin_b"); // Then intermediate
|
|
|
|
|
EXPECT_EQ(result[2].toStdString(), "plugin_a"); // Then requested plugin
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// C API: logos_core_load_plugin_with_dependencies Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// Verifies that logos_core_load_plugin_with_dependencies() returns 0 for null plugin name
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPluginWithDependencies_ReturnsZeroForNull) {
|
|
|
|
|
int result = logos_core_load_plugin_with_dependencies(nullptr);
|
|
|
|
|
EXPECT_EQ(result, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that logos_core_load_plugin_with_dependencies() returns 0 for unknown plugin
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPluginWithDependencies_ReturnsZeroForUnknown) {
|
|
|
|
|
int result = logos_core_load_plugin_with_dependencies("unknown_plugin");
|
|
|
|
|
EXPECT_EQ(result, 0);
|
|
|
|
|
}
|
2026-02-03 16:37:59 -05:00
|
|
|
|
|
|
|
|
// Verifies that logos_core_load_plugin_with_dependencies() skips already-loaded plugins
|
|
|
|
|
TEST_F(PluginManagerTest, LoadPluginWithDependencies_SkipsAlreadyLoaded) {
|
|
|
|
|
// Set up plugin_a which depends on plugin_b
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_a", "/path/to/plugin_a");
|
|
|
|
|
PluginManager::addKnownPlugin("plugin_b", "/path/to/plugin_b");
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataA;
|
|
|
|
|
metadataA["name"] = "plugin_a";
|
|
|
|
|
QJsonArray depsA;
|
|
|
|
|
depsA.append("plugin_b");
|
|
|
|
|
metadataA["dependencies"] = depsA;
|
|
|
|
|
g_plugin_metadata.insert("plugin_a", metadataA);
|
|
|
|
|
|
|
|
|
|
QJsonObject metadataB;
|
|
|
|
|
metadataB["name"] = "plugin_b";
|
|
|
|
|
metadataB["dependencies"] = QJsonArray();
|
|
|
|
|
g_plugin_metadata.insert("plugin_b", metadataB);
|
|
|
|
|
|
|
|
|
|
// Mark plugin_b as already loaded
|
|
|
|
|
g_loaded_plugins.append("plugin_b");
|
|
|
|
|
|
|
|
|
|
// Verify the skip logic preconditions
|
|
|
|
|
EXPECT_TRUE(PluginManager::isPluginLoaded("plugin_b"));
|
|
|
|
|
EXPECT_FALSE(PluginManager::isPluginLoaded("plugin_a"));
|
|
|
|
|
|
|
|
|
|
// The function should skip plugin_b (already loaded) and only attempt to load plugin_a
|
|
|
|
|
// Since plugin_a doesn't have a real file, loading will fail for plugin_a specifically,
|
|
|
|
|
// but the important thing is plugin_b is skipped (not double-loaded)
|
|
|
|
|
int result = logos_core_load_plugin_with_dependencies("plugin_a");
|
|
|
|
|
|
|
|
|
|
// Result will be 0 because plugin_a can't actually be loaded (no real file),
|
|
|
|
|
// but plugin_b should still be in loaded state (not removed or errored)
|
|
|
|
|
EXPECT_TRUE(PluginManager::isPluginLoaded("plugin_b"));
|
|
|
|
|
}
|