diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index e4e0c3eea67f8..a6dab045adf27 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -22,6 +22,7 @@ #include #include +#include #define LLDB_PLUGIN_DEFINE_ADV(ClassName, PluginName) \ namespace lldb_private { \ @@ -47,6 +48,12 @@ class CommandInterpreter; class Debugger; class StringList; +struct RegisteredPluginInfo { + llvm::StringRef name = ""; + llvm::StringRef description = ""; + bool enabled = false; +}; + class PluginManager { public: static void Initialize(); @@ -168,6 +175,12 @@ class PluginManager { static SystemRuntimeCreateInstance GetSystemRuntimeCreateCallbackAtIndex(uint32_t idx); + static std::vector GetSystemRuntimePluginInfo(); + + // Modify the enabled state of a SystemRuntime plugin. + // Returns false if the plugin name is not found. + static bool SetSystemRuntimePluginEnabled(llvm::StringRef name, bool enabled); + // ObjectFile static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp index f3108b8a768d2..68261d24ffe1f 100644 --- a/lldb/source/Commands/CommandObjectPlugin.cpp +++ b/lldb/source/Commands/CommandObjectPlugin.cpp @@ -7,8 +7,11 @@ //===----------------------------------------------------------------------===// #include "CommandObjectPlugin.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" +#include "llvm/Support/GlobPattern.h" using namespace lldb; using namespace lldb_private; @@ -46,12 +49,344 @@ class CommandObjectPluginLoad : public CommandObjectParsed { } }; +namespace { +#define LLDB_OPTIONS_plugin_list +#include "CommandOptions.inc" + +// These option definitions are shared by the plugin list/enable/disable +// commands. +class PluginListCommandOptions : public Options { +public: + PluginListCommandOptions() = default; + + ~PluginListCommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'x': + m_exact_name_match = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_exact_name_match = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::ArrayRef(g_plugin_list_options); + } + + // Instance variables to hold the values for command options. + bool m_exact_name_match = false; +}; + +// Define some data structures to describe known plugin "namespaces". +// The PluginManager is organized into a series of static functions +// that operate on different types of plugin. For example SystemRuntime +// and ObjectFile plugins. +// +// The namespace name is used a prefix when matching plugin names. For example, +// if we have an "elf" plugin in the "object-file" namespace then we will +// match a plugin name pattern against the "object-file.elf" name. +// +// The plugin namespace here is used so we can operate on all the plugins +// of a given type so it is easy to enable or disable them as a group. +using GetPluginInfo = std::function()>; +using SetPluginEnabled = std::function; +struct PluginNamespace { + llvm::StringRef name; + GetPluginInfo get_info; + SetPluginEnabled set_enabled; +}; + +// Currently supported set of plugin namespaces. This will be expanded +// over time. +PluginNamespace PluginNamespaces[] = { + {"system-runtime", PluginManager::GetSystemRuntimePluginInfo, + PluginManager::SetSystemRuntimePluginEnabled}}; + +// Helper function to perform an action on each matching plugin. +// The action callback is given the containing namespace along with plugin info +// for each matching plugin. +static int ActOnMatchingPlugins( + llvm::GlobPattern pattern, + std::function &plugin_info)> + action) { + int num_matching = 0; + + for (const PluginNamespace &plugin_namespace : PluginNamespaces) { + std::vector all_plugins = plugin_namespace.get_info(); + std::vector matching_plugins; + for (const RegisteredPluginInfo &plugin_info : all_plugins) { + std::string qualified_name = + (plugin_namespace.name + "." + plugin_info.name).str(); + if (pattern.match(qualified_name)) { + matching_plugins.push_back(plugin_info); + } + } + + if (!matching_plugins.empty()) { + num_matching += matching_plugins.size(); + action(plugin_namespace, matching_plugins); + } + } + + return num_matching; +} + +// Return a string in glob syntax for matching plugins. +static std::string GetPluginNamePatternString(llvm::StringRef user_input, + bool add_default_glob) { + std::string pattern_str; + if (user_input.empty()) + pattern_str = "*"; + else + pattern_str = user_input; + + if (add_default_glob && pattern_str != "*") { + pattern_str = "*" + pattern_str + "*"; + } + + return pattern_str; +} + +// Attempts to create a glob pattern for a plugin name based on plugin command +// input. Writes an error message to the `result` object if the glob cannot be +// created successfully. +// +// The `glob_storage` is used to hold the string data for the glob pattern. The +// llvm::GlobPattern only contains pointers into the string data so we need a +// stable location that can outlive the glob pattern itself. +std::optional +TryCreatePluginPattern(const char *plugin_command_name, const Args &command, + const PluginListCommandOptions &options, + CommandReturnObject &result, std::string &glob_storage) { + size_t argc = command.GetArgumentCount(); + if (argc > 1) { + result.AppendErrorWithFormat("'%s' requires one argument", + plugin_command_name); + return {}; + } + + llvm::StringRef user_pattern; + if (argc == 1) { + user_pattern = command[0].ref(); + } + + glob_storage = + GetPluginNamePatternString(user_pattern, !options.m_exact_name_match); + + auto glob_pattern = llvm::GlobPattern::create(glob_storage); + + if (auto error = glob_pattern.takeError()) { + std::string error_message = + (llvm::Twine("Invalid plugin glob pattern: '") + glob_storage + + "': " + llvm::toString(std::move(error))) + .str(); + result.AppendError(error_message); + return {}; + } + + return *glob_pattern; +} + +// Call the "SetEnable" function for each matching plugins. +// Used to share the majority of the code between the enable +// and disable commands. +int SetEnableOnMatchingPlugins(const llvm::GlobPattern &pattern, + CommandReturnObject &result, bool enabled) { + return ActOnMatchingPlugins( + pattern, [&](const PluginNamespace &plugin_namespace, + const std::vector &plugins) { + result.AppendMessage(plugin_namespace.name); + for (const auto &plugin : plugins) { + if (!plugin_namespace.set_enabled(plugin.name, enabled)) { + result.AppendErrorWithFormat("failed to enable plugin %s.%s", + plugin_namespace.name.data(), + plugin.name.data()); + continue; + } + + result.AppendMessageWithFormat( + " %s %-30s %s\n", enabled ? "[+]" : "[-]", plugin.name.data(), + plugin.description.data()); + } + }); +} +} // namespace + +class CommandObjectPluginList : public CommandObjectParsed { +public: + CommandObjectPluginList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin list", + "Report info about registered LLDB plugins.", + nullptr) { + AddSimpleArgumentList(eArgTypePlugin); + SetHelpLong(R"( +Display information about registered plugins. +The plugin information is formatted as shown below + + + [+] Plugin #1 description + [-] Plugin #2 description + +An enabled plugin is marked with [+] and a disabled plugin is marked with [-]. + +Selecting plugins +------------------ +plugin list [.][] + +Plugin names are specified using glob patterns. The pattern will be matched +against the plugins fully qualified name, which is composed of the namespace, +followed by a '.', followed by the plugin name. + +When no arguments are given the plugin selection string is the wildcard '*'. +By default wildcards are added around the input to enable searching by +substring. You can prevent these implicit wild cards by using the +-x flag. + +Examples +----------------- +List all plugins in the system-runtime namespace + + (lldb) plugin list system-runtime.* + +List all plugins containing the string foo + + (lldb) plugin list foo + +This is equivalent to + + (lldb) plugin list *foo* + +List only a plugin matching a fully qualified name exactly + + (lldb) plugin list -x system-runtime.foo +)"); + } + + ~CommandObjectPluginList() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + std::string glob_storage; + std::optional plugin_glob = TryCreatePluginPattern( + "plugin list", command, m_options, result, glob_storage); + + if (!plugin_glob) { + assert(!result.Succeeded()); + return; + } + + int num_matching = ActOnMatchingPlugins( + *plugin_glob, [&](const PluginNamespace &plugin_namespace, + const std::vector &plugins) { + result.AppendMessage(plugin_namespace.name); + for (auto &plugin : plugins) { + result.AppendMessageWithFormat( + " %s %-30s %s\n", plugin.enabled ? "[+]" : "[-]", + plugin.name.data(), plugin.description.data()); + } + }); + + if (num_matching == 0) { + result.AppendErrorWithFormat("Found no matching plugins"); + } + } + + PluginListCommandOptions m_options; +}; + +class CommandObjectPluginEnable : public CommandObjectParsed { +public: + CommandObjectPluginEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin enable", + "Enable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypePlugin); + } + + ~CommandObjectPluginEnable() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + std::string glob_storage; + std::optional plugin_glob = TryCreatePluginPattern( + "plugin enable", command, m_options, result, glob_storage); + + if (!plugin_glob) { + assert(!result.Succeeded()); + return; + } + + int num_matching = SetEnableOnMatchingPlugins(*plugin_glob, result, true); + + if (num_matching == 0) { + result.AppendErrorWithFormat("Found no matching plugins to enable"); + } + } + + PluginListCommandOptions m_options; +}; + +class CommandObjectPluginDisable : public CommandObjectParsed { +public: + CommandObjectPluginDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin disable", + "Disable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypePlugin); + } + + ~CommandObjectPluginDisable() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + std::string glob_storage; + std::optional plugin_glob = TryCreatePluginPattern( + "plugin disable", command, m_options, result, glob_storage); + + if (!plugin_glob) { + assert(!result.Succeeded()); + return; + } + + int num_matching = SetEnableOnMatchingPlugins(*plugin_glob, result, false); + + if (num_matching == 0) { + result.AppendErrorWithFormat("Found no matching plugins to disable"); + } + } + + PluginListCommandOptions m_options; +}; + CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "plugin", "Commands for managing LLDB plugins.", "plugin []") { LoadSubCommand("load", CommandObjectSP(new CommandObjectPluginLoad(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectPluginList(interpreter))); + LoadSubCommand("enable", + CommandObjectSP(new CommandObjectPluginEnable(interpreter))); + LoadSubCommand("disable", + CommandObjectSP(new CommandObjectPluginDisable(interpreter))); } CommandObjectPlugin::~CommandObjectPlugin() = default; diff --git a/lldb/source/Commands/CommandObjectSettings.cpp b/lldb/source/Commands/CommandObjectSettings.cpp index 7bbb0dd567ab1..1b60d441264d6 100644 --- a/lldb/source/Commands/CommandObjectSettings.cpp +++ b/lldb/source/Commands/CommandObjectSettings.cpp @@ -15,6 +15,7 @@ #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandOptionArgumentTable.h" #include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionValue.h" #include "lldb/Interpreter/OptionValueProperties.h" using namespace lldb; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index cc579d767eb06..ac8fd78f4a894 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -683,6 +683,11 @@ let Command = "platform shell" in { Desc<"Shell interpreter path. This is the binary used to run the command.">; } +let Command = "plugin list" in { + def plugin_info_exact : Option<"exact", "x">, + Desc<"Do not add implicit * glob around plugin name">; +} + let Command = "process launch" in { def process_launch_stop_at_entry : Option<"stop-at-entry", "s">, Desc<"Stop at the entry point of the program when launching a process.">; diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index 80c9465f9af72..e6cb248ef31ce 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -188,11 +188,13 @@ template struct PluginInstance { PluginInstance(llvm::StringRef name, llvm::StringRef description, Callback create_callback, DebuggerInitializeCallback debugger_init_callback = nullptr) - : name(name), description(description), create_callback(create_callback), + : name(name), description(description), enabled(true), + create_callback(create_callback), debugger_init_callback(debugger_init_callback) {} llvm::StringRef name; llvm::StringRef description; + bool enabled; Callback create_callback; DebuggerInitializeCallback debugger_init_callback; }; @@ -226,49 +228,104 @@ template class PluginInstances { } typename Instance::CallbackType GetCallbackAtIndex(uint32_t idx) { - if (Instance *instance = GetInstanceAtIndex(idx)) + if (const Instance *instance = GetInstanceAtIndex(idx)) return instance->create_callback; return nullptr; } llvm::StringRef GetDescriptionAtIndex(uint32_t idx) { - if (Instance *instance = GetInstanceAtIndex(idx)) + if (const Instance *instance = GetInstanceAtIndex(idx)) return instance->description; return ""; } llvm::StringRef GetNameAtIndex(uint32_t idx) { - if (Instance *instance = GetInstanceAtIndex(idx)) + if (const Instance *instance = GetInstanceAtIndex(idx)) return instance->name; return ""; } typename Instance::CallbackType GetCallbackForName(llvm::StringRef name) { - if (name.empty()) - return nullptr; - for (auto &instance : m_instances) { - if (name == instance.name) - return instance.create_callback; - } + if (const Instance *instance = GetInstanceForName(name)) + return instance->create_callback; return nullptr; } void PerformDebuggerCallback(Debugger &debugger) { - for (auto &instance : m_instances) { + for (const auto &instance : m_instances) { + if (!instance.enabled) + continue; if (instance.debugger_init_callback) instance.debugger_init_callback(debugger); } } - const std::vector &GetInstances() const { return m_instances; } - std::vector &GetInstances() { return m_instances; } + // Return a copy of all the enabled instances. + // Note that this is a copy of the internal state so modifications + // to the returned instances will not be reflected back to instances + // stored by the PluginInstances object. + std::vector GetSnapshot() { + std::vector enabled_instances; + for (const auto &instance : m_instances) { + if (instance.enabled) + enabled_instances.push_back(instance); + } + return enabled_instances; + } + + const Instance *GetInstanceAtIndex(uint32_t idx) { + uint32_t count = 0; + + return FindEnabledInstance( + [&](const Instance &instance) { return count++ == idx; }); + } + + const Instance *GetInstanceForName(llvm::StringRef name) { + if (name.empty()) + return nullptr; + + return FindEnabledInstance( + [&](const Instance &instance) { return instance.name == name; }); + } - Instance *GetInstanceAtIndex(uint32_t idx) { - if (idx < m_instances.size()) - return &m_instances[idx]; + const Instance * + FindEnabledInstance(std::function predicate) const { + for (const auto &instance : m_instances) { + if (!instance.enabled) + continue; + if (predicate(instance)) + return &instance; + } return nullptr; } + // Return a list of all the registered plugin instances. This includes both + // enabled and disabled instances. The instances are listed in the order they + // were registered which is the order they would be queried if they were all + // enabled. + std::vector GetPluginInfoForAllInstances() { + // Lookup the plugin info for each instance in the sorted order. + std::vector plugin_infos; + plugin_infos.reserve(m_instances.size()); + for (const Instance &instance : m_instances) + plugin_infos.push_back( + {instance.name, instance.description, instance.enabled}); + + return plugin_infos; + } + + bool SetInstanceEnabled(llvm::StringRef name, bool enable) { + auto it = std::find_if( + m_instances.begin(), m_instances.end(), + [&](const Instance &instance) { return instance.name == name; }); + + if (it == m_instances.end()) + return false; + + it->enabled = enable; + return true; + } + private: std::vector m_instances; }; @@ -571,17 +628,15 @@ PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(uint32_t idx) { LanguageRuntimeGetCommandObject PluginManager::GetLanguageRuntimeGetCommandObjectAtIndex(uint32_t idx) { - const auto &instances = GetLanguageRuntimeInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].command_callback; + if (auto instance = GetLanguageRuntimeInstances().GetInstanceAtIndex(idx)) + return instance->command_callback; return nullptr; } LanguageRuntimeGetExceptionPrecondition PluginManager::GetLanguageRuntimeGetExceptionPreconditionAtIndex(uint32_t idx) { - const auto &instances = GetLanguageRuntimeInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].precondition_callback; + if (auto instance = GetLanguageRuntimeInstances().GetInstanceAtIndex(idx)) + return instance->precondition_callback; return nullptr; } @@ -612,6 +667,15 @@ PluginManager::GetSystemRuntimeCreateCallbackAtIndex(uint32_t idx) { return GetSystemRuntimeInstances().GetCallbackAtIndex(idx); } +std::vector PluginManager::GetSystemRuntimePluginInfo() { + return GetSystemRuntimeInstances().GetPluginInfoForAllInstances(); +} + +bool PluginManager::SetSystemRuntimePluginEnabled(llvm::StringRef name, + bool enable) { + return GetSystemRuntimeInstances().SetInstanceEnabled(name, enable); +} + #pragma mark ObjectFile struct ObjectFileInstance : public PluginInstance { @@ -643,12 +707,7 @@ bool PluginManager::IsRegisteredObjectFilePluginName(llvm::StringRef name) { if (name.empty()) return false; - const auto &instances = GetObjectFileInstances().GetInstances(); - for (auto &instance : instances) { - if (instance.name == name) - return true; - } - return false; + return GetObjectFileInstances().GetInstanceForName(name) != nullptr; } bool PluginManager::RegisterPlugin( @@ -674,29 +733,24 @@ PluginManager::GetObjectFileCreateCallbackAtIndex(uint32_t idx) { ObjectFileCreateMemoryInstance PluginManager::GetObjectFileCreateMemoryCallbackAtIndex(uint32_t idx) { - const auto &instances = GetObjectFileInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].create_memory_callback; + if (auto instance = GetObjectFileInstances().GetInstanceAtIndex(idx)) + return instance->create_memory_callback; return nullptr; } ObjectFileGetModuleSpecifications PluginManager::GetObjectFileGetModuleSpecificationsCallbackAtIndex( uint32_t idx) { - const auto &instances = GetObjectFileInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].get_module_specifications; + if (auto instance = GetObjectFileInstances().GetInstanceAtIndex(idx)) + return instance->get_module_specifications; return nullptr; } ObjectFileCreateMemoryInstance PluginManager::GetObjectFileCreateMemoryCallbackForPluginName( llvm::StringRef name) { - const auto &instances = GetObjectFileInstances().GetInstances(); - for (auto &instance : instances) { - if (instance.name == name) - return instance.create_memory_callback; - } + if (auto instance = GetObjectFileInstances().GetInstanceForName(name)) + return instance->create_memory_callback; return nullptr; } @@ -729,7 +783,7 @@ Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp, // Fall back to object plugins. const auto &plugin_name = options.GetPluginName().value_or(""); - auto &instances = GetObjectFileInstances().GetInstances(); + auto instances = GetObjectFileInstances().GetSnapshot(); for (auto &instance : instances) { if (plugin_name.empty() || instance.name == plugin_name) { if (instance.save_core && instance.save_core(process_sp, options, error)) @@ -791,18 +845,16 @@ PluginManager::GetObjectContainerCreateCallbackAtIndex(uint32_t idx) { ObjectContainerCreateMemoryInstance PluginManager::GetObjectContainerCreateMemoryCallbackAtIndex(uint32_t idx) { - const auto &instances = GetObjectContainerInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].create_memory_callback; + if (auto instance = GetObjectContainerInstances().GetInstanceAtIndex(idx)) + return instance->create_memory_callback; return nullptr; } ObjectFileGetModuleSpecifications PluginManager::GetObjectContainerGetModuleSpecificationsCallbackAtIndex( uint32_t idx) { - const auto &instances = GetObjectContainerInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].get_module_specifications; + if (auto instance = GetObjectContainerInstances().GetInstanceAtIndex(idx)) + return instance->get_module_specifications; return nullptr; } @@ -849,7 +901,7 @@ PluginManager::GetPlatformCreateCallbackForPluginName(llvm::StringRef name) { void PluginManager::AutoCompletePlatformName(llvm::StringRef name, CompletionRequest &request) { - for (const auto &instance : GetPlatformInstances().GetInstances()) { + for (const auto &instance : GetPlatformInstances().GetSnapshot()) { if (instance.name.starts_with(name)) request.AddCompletion(instance.name); } @@ -897,7 +949,7 @@ PluginManager::GetProcessCreateCallbackForPluginName(llvm::StringRef name) { void PluginManager::AutoCompleteProcessName(llvm::StringRef name, CompletionRequest &request) { - for (const auto &instance : GetProcessInstances().GetInstances()) { + for (const auto &instance : GetProcessInstances().GetSnapshot()) { if (instance.name.starts_with(name)) request.AddCompletion(instance.name, instance.description); } @@ -935,11 +987,11 @@ bool PluginManager::UnregisterPlugin( lldb::RegisterTypeBuilderSP PluginManager::GetRegisterTypeBuilder(Target &target) { - const auto &instances = GetRegisterTypeBuilderInstances().GetInstances(); // We assume that RegisterTypeBuilderClang is the only instance of this plugin // type and is always present. - assert(instances.size()); - return instances[0].create_callback(target); + auto instance = GetRegisterTypeBuilderInstances().GetInstanceAtIndex(0); + assert(instance); + return instance->create_callback(target); } #pragma mark ScriptInterpreter @@ -984,7 +1036,7 @@ PluginManager::GetScriptInterpreterCreateCallbackAtIndex(uint32_t idx) { lldb::ScriptInterpreterSP PluginManager::GetScriptInterpreterForLanguage(lldb::ScriptLanguage script_lang, Debugger &debugger) { - const auto &instances = GetScriptInterpreterInstances().GetInstances(); + const auto instances = GetScriptInterpreterInstances().GetSnapshot(); ScriptInterpreterCreateInstance none_instance = nullptr; for (const auto &instance : instances) { if (instance.language == lldb::eScriptLanguageNone) @@ -1046,13 +1098,12 @@ PluginManager::GetStructuredDataPluginCreateCallbackAtIndex(uint32_t idx) { StructuredDataFilterLaunchInfo PluginManager::GetStructuredDataFilterCallbackAtIndex( uint32_t idx, bool &iteration_complete) { - const auto &instances = GetStructuredDataPluginInstances().GetInstances(); - if (idx < instances.size()) { + if (auto instance = + GetStructuredDataPluginInstances().GetInstanceAtIndex(idx)) { iteration_complete = false; - return instances[idx].filter_callback; - } else { - iteration_complete = true; + return instance->filter_callback; } + iteration_complete = true; return nullptr; } @@ -1167,7 +1218,7 @@ PluginManager::GetSymbolLocatorCreateCallbackAtIndex(uint32_t idx) { ModuleSpec PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec) { - auto &instances = GetSymbolLocatorInstances().GetInstances(); + auto instances = GetSymbolLocatorInstances().GetSnapshot(); for (auto &instance : instances) { if (instance.locate_executable_object_file) { std::optional result = @@ -1181,7 +1232,7 @@ PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec) { FileSpec PluginManager::LocateExecutableSymbolFile( const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { - auto &instances = GetSymbolLocatorInstances().GetInstances(); + auto instances = GetSymbolLocatorInstances().GetSnapshot(); for (auto &instance : instances) { if (instance.locate_executable_symbol_file) { std::optional result = instance.locate_executable_symbol_file( @@ -1197,7 +1248,7 @@ bool PluginManager::DownloadObjectAndSymbolFile(ModuleSpec &module_spec, Status &error, bool force_lookup, bool copy_executable) { - auto &instances = GetSymbolLocatorInstances().GetInstances(); + auto instances = GetSymbolLocatorInstances().GetSnapshot(); for (auto &instance : instances) { if (instance.download_object_symbol_file) { if (instance.download_object_symbol_file(module_spec, error, force_lookup, @@ -1211,7 +1262,7 @@ bool PluginManager::DownloadObjectAndSymbolFile(ModuleSpec &module_spec, FileSpec PluginManager::FindSymbolFileInBundle(const FileSpec &symfile_bundle, const UUID *uuid, const ArchSpec *arch) { - auto &instances = GetSymbolLocatorInstances().GetInstances(); + auto instances = GetSymbolLocatorInstances().GetSnapshot(); for (auto &instance : instances) { if (instance.find_symbol_file_in_bundle) { std::optional result = @@ -1272,21 +1323,20 @@ PluginManager::GetTraceCreateCallback(llvm::StringRef plugin_name) { TraceCreateInstanceForLiveProcess PluginManager::GetTraceCreateCallbackForLiveProcess(llvm::StringRef plugin_name) { - for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) - if (instance.name == plugin_name) - return instance.create_callback_for_live_process; + if (auto instance = GetTracePluginInstances().GetInstanceForName(plugin_name)) + return instance->create_callback_for_live_process; + return nullptr; } llvm::StringRef PluginManager::GetTraceSchema(llvm::StringRef plugin_name) { - for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) - if (instance.name == plugin_name) - return instance.schema; + if (auto instance = GetTracePluginInstances().GetInstanceForName(plugin_name)) + return instance->schema; return llvm::StringRef(); } llvm::StringRef PluginManager::GetTraceSchema(size_t index) { - if (TraceInstance *instance = + if (const TraceInstance *instance = GetTracePluginInstances().GetInstanceAtIndex(index)) return instance->schema; return llvm::StringRef(); @@ -1335,7 +1385,7 @@ bool PluginManager::UnregisterPlugin( ThreadTraceExportCommandCreator PluginManager::GetThreadTraceExportCommandCreatorAtIndex(uint32_t index) { - if (TraceExporterInstance *instance = + if (const TraceExporterInstance *instance = GetTraceExporterInstances().GetInstanceAtIndex(index)) return instance->create_thread_trace_export_command; return nullptr; @@ -1438,9 +1488,9 @@ bool PluginManager::UnregisterPlugin( InstrumentationRuntimeGetType PluginManager::GetInstrumentationRuntimeGetTypeCallbackAtIndex(uint32_t idx) { - const auto &instances = GetInstrumentationRuntimeInstances().GetInstances(); - if (idx < instances.size()) - return instances[idx].get_type_callback; + if (auto instance = + GetInstrumentationRuntimeInstances().GetInstanceAtIndex(idx)) + return instance->get_type_callback; return nullptr; } @@ -1493,7 +1543,7 @@ PluginManager::GetTypeSystemCreateCallbackAtIndex(uint32_t idx) { } LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForTypes() { - const auto &instances = GetTypeSystemInstances().GetInstances(); + const auto instances = GetTypeSystemInstances().GetSnapshot(); LanguageSet all; for (unsigned i = 0; i < instances.size(); ++i) all.bitvector |= instances[i].supported_languages_for_types.bitvector; @@ -1501,7 +1551,7 @@ LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForTypes() { } LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForExpressions() { - const auto &instances = GetTypeSystemInstances().GetInstances(); + const auto instances = GetTypeSystemInstances().GetSnapshot(); LanguageSet all; for (unsigned i = 0; i < instances.size(); ++i) all.bitvector |= instances[i].supported_languages_for_expressions.bitvector; @@ -1545,7 +1595,7 @@ bool PluginManager::UnregisterPlugin( } uint32_t PluginManager::GetNumScriptedInterfaces() { - return GetScriptedInterfaceInstances().GetInstances().size(); + return GetScriptedInterfaceInstances().GetSnapshot().size(); } llvm::StringRef PluginManager::GetScriptedInterfaceNameAtIndex(uint32_t index) { @@ -1559,17 +1609,16 @@ PluginManager::GetScriptedInterfaceDescriptionAtIndex(uint32_t index) { lldb::ScriptLanguage PluginManager::GetScriptedInterfaceLanguageAtIndex(uint32_t idx) { - const auto &instances = GetScriptedInterfaceInstances().GetInstances(); - return idx < instances.size() ? instances[idx].language - : ScriptLanguage::eScriptLanguageNone; + if (auto instance = GetScriptedInterfaceInstances().GetInstanceAtIndex(idx)) + return instance->language; + return ScriptLanguage::eScriptLanguageNone; } ScriptedInterfaceUsages PluginManager::GetScriptedInterfaceUsagesAtIndex(uint32_t idx) { - const auto &instances = GetScriptedInterfaceInstances().GetInstances(); - if (idx >= instances.size()) - return {}; - return instances[idx].usages; + if (auto instance = GetScriptedInterfaceInstances().GetInstanceAtIndex(idx)) + return instance->usages; + return {}; } #pragma mark REPL @@ -1606,13 +1655,13 @@ REPLCreateInstance PluginManager::GetREPLCreateCallbackAtIndex(uint32_t idx) { } LanguageSet PluginManager::GetREPLSupportedLanguagesAtIndex(uint32_t idx) { - const auto &instances = GetREPLInstances().GetInstances(); - return idx < instances.size() ? instances[idx].supported_languages - : LanguageSet(); + if (auto instance = GetREPLInstances().GetInstanceAtIndex(idx)) + return instance->supported_languages; + return LanguageSet(); } LanguageSet PluginManager::GetREPLAllTypeSystemSupportedLanguages() { - const auto &instances = GetREPLInstances().GetInstances(); + const auto instances = GetREPLInstances().GetSnapshot(); LanguageSet all; for (unsigned i = 0; i < instances.size(); ++i) all.bitvector |= instances[i].supported_languages.bitvector; diff --git a/lldb/test/Shell/Commands/command-plugin-enable+disable.test b/lldb/test/Shell/Commands/command-plugin-enable+disable.test new file mode 100644 index 0000000000000..fcc1994b7697c --- /dev/null +++ b/lldb/test/Shell/Commands/command-plugin-enable+disable.test @@ -0,0 +1,53 @@ +# This test validates the plugin enable and disable commands. +# Currently it works only for system-runtime plugins and we only have one +# system runtime plugin so testing is a bit limited. +# +# Note that commands that return errors will stop running a script, so we +# have new RUN lines for any command that is expected to return an error. + +# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s + +# Test plugin list shows the default state which is expected to be enabled. +plugin list +# CHECK-LABEL: plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin disable disables a plugin. +plugin disable systemruntime-macosx +# CHECK-LABEL: plugin disable systemruntime-macosx +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Make sure plugin list shows it disabled as well. +plugin list +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable re-enables a plugin. +plugin enable systemruntime-macosx +# CHECK-LABEL: plugin enable systemruntime-macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Make sure plugin list shows it enabled as well. +plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin disable with wildcard works. +plugin disable system* +# CHECK-LABEL: plugin disable system* +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable with wildcard works. +plugin enable system* +# CHECK-LABEL: plugin enable system* +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable/disable for unknown plugin returns an error. +# RUN: %lldb -o "plugin enable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# RUN: %lldb -o "plugin disable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins diff --git a/lldb/test/Shell/Commands/command-plugin-list.test b/lldb/test/Shell/Commands/command-plugin-list.test new file mode 100644 index 0000000000000..7e5c9c671a783 --- /dev/null +++ b/lldb/test/Shell/Commands/command-plugin-list.test @@ -0,0 +1,51 @@ +# This test validates the plugin list command. +# Currently it works only for system-runtime plugins and we only have one +# system runtime plugin so testing is a bit limited. +# +# Note that commands that return errors will stop running a script, so we +# have new RUN lines for any command that is expected to return an error. + +# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s + +# Test plugin list without an argument will list all plugins. +plugin list +# CHECK-LABEL: plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin list with an argument will match a plugin name by substring. +plugin list macosx +# CHECK-LABEL: plugin list macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin exact list works with fully qualified name. +plugin list -x system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin list -x system-runtime.systemruntime-macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin glob list works with fully qualified name. +plugin list system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin list system-runtime.systemruntime-macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin exact list still allows glob patterns. +plugin list -x system* +# CHECK-LABEL: plugin list -x system* +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin glob list still allows glob patterns. +plugin list system-runtime.* +# CHECK-LABEL: plugin list system-runtime.* +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin list can disable implicit glob patterns. +# RUN: %lldb -o "plugin list -x macosx" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list for unknown plugin returns an error. +# RUN: %lldb -o "plugin list some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt index 60265f794b5e8..8580f5887ea2b 100644 --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -7,6 +7,7 @@ add_lldb_unittest(LLDBCoreTests FormatEntityTest.cpp MangledTest.cpp ModuleSpecTest.cpp + PluginManagerTest.cpp ProgressReportTest.cpp RichManglingContextTest.cpp SourceLocationSpecTest.cpp diff --git a/lldb/unittests/Core/PluginManagerTest.cpp b/lldb/unittests/Core/PluginManagerTest.cpp new file mode 100644 index 0000000000000..ca1003ca9a85a --- /dev/null +++ b/lldb/unittests/Core/PluginManagerTest.cpp @@ -0,0 +1,367 @@ + +#include "lldb/Core/PluginManager.h" + +#include "gtest/gtest.h" + +using namespace lldb; +using namespace lldb_private; + +// Mock system runtime plugin create functions. +SystemRuntime *CreateSystemRuntimePluginA(Process *process) { return nullptr; } + +SystemRuntime *CreateSystemRuntimePluginB(Process *process) { return nullptr; } + +SystemRuntime *CreateSystemRuntimePluginC(Process *process) { return nullptr; } + +// Test class for testing the PluginManager. +// The PluginManager modifies global state when registering new plugins. This +// class is intended to undo those modifications in the destructor to give each +// test a clean slate with no registered plugins at the start of a test. +class PluginManagerTest : public testing::Test { +public: + // Remove any pre-registered plugins so we have a known starting point. + static void SetUpTestSuite() { RemoveAllRegisteredSystemRuntimePlugins(); } + + // Add mock system runtime plugins for testing. + void RegisterMockSystemRuntimePlugins() { + ASSERT_TRUE(PluginManager::RegisterPlugin("a", "test instance A", + CreateSystemRuntimePluginA)); + ASSERT_TRUE(PluginManager::RegisterPlugin("b", "test instance B", + CreateSystemRuntimePluginB)); + ASSERT_TRUE(PluginManager::RegisterPlugin("c", "test instance C", + CreateSystemRuntimePluginC)); + } + + // Remove any plugins added during the tests. + virtual ~PluginManagerTest() override { + RemoveAllRegisteredSystemRuntimePlugins(); + } + +protected: + std::vector m_system_runtime_plugins; + + static void RemoveAllRegisteredSystemRuntimePlugins() { + // Enable all currently registered plugins so we can get a handle to + // their create callbacks in the loop below. Only enabled plugins + // are returned from the PluginManager Get*CreateCallbackAtIndex apis. + for (const RegisteredPluginInfo &PluginInfo : + PluginManager::GetSystemRuntimePluginInfo()) { + PluginManager::SetSystemRuntimePluginEnabled(PluginInfo.name, true); + } + + // Get a handle to the create call backs for all the registered plugins. + std::vector registered_plugin_callbacks; + SystemRuntimeCreateInstance create_callback = nullptr; + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetSystemRuntimeCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + registered_plugin_callbacks.push_back((create_callback)); + } + + // Remove all currently registered plugins. + for (SystemRuntimeCreateInstance create_callback : + registered_plugin_callbacks) { + PluginManager::UnregisterPlugin(create_callback); + } + } +}; + +// Test basic register functionality. +TEST_F(PluginManagerTest, RegisterSystemRuntimePlugin) { + RegisterMockSystemRuntimePlugins(); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginC); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(3), nullptr); +} + +// Test basic un-register functionality. +TEST_F(PluginManagerTest, UnRegisterSystemRuntimePlugin) { + RegisterMockSystemRuntimePlugins(); + + ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB)); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr); +} + +// Test registered plugin info functionality. +TEST_F(PluginManagerTest, SystemRuntimePluginInfo) { + RegisterMockSystemRuntimePlugins(); + + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].description, "test instance A"); + ASSERT_EQ(plugin_info[0].enabled, true); + ASSERT_EQ(plugin_info[1].name, "b"); + ASSERT_EQ(plugin_info[1].description, "test instance B"); + ASSERT_EQ(plugin_info[1].enabled, true); + ASSERT_EQ(plugin_info[2].name, "c"); + ASSERT_EQ(plugin_info[2].description, "test instance C"); + ASSERT_EQ(plugin_info[2].enabled, true); +} + +// Test basic un-register functionality. +TEST_F(PluginManagerTest, UnRegisterSystemRuntimePluginInfo) { + RegisterMockSystemRuntimePlugins(); + + // Initial plugin info has all three registered plugins. + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + + ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB)); + + // After un-registering a plugin it should be removed from plugin info. + plugin_info = PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 2u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].enabled, true); + ASSERT_EQ(plugin_info[1].name, "c"); + ASSERT_EQ(plugin_info[1].enabled, true); +} + +// Test plugin disable functionality. +TEST_F(PluginManagerTest, SystemRuntimePluginDisable) { + RegisterMockSystemRuntimePlugins(); + + // Disable plugin should succeed. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + + // Disabling a plugin does not remove it from plugin info. + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].enabled, true); + ASSERT_EQ(plugin_info[1].name, "b"); + ASSERT_EQ(plugin_info[1].enabled, false); + ASSERT_EQ(plugin_info[2].name, "c"); + ASSERT_EQ(plugin_info[2].enabled, true); + + // Disabling a plugin does remove it from available plugins. + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr); +} + +// Test plugin disable and enable functionality. +TEST_F(PluginManagerTest, SystemRuntimePluginDisableThenEnable) { + RegisterMockSystemRuntimePlugins(); + + // Initially plugin b is available in slot 1. + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + + // Disabling it will remove it from available plugins. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + + // We can re-enable the plugin later and it should go back to the original + // slot. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginC); + + // And show up in the plugin info correctly. + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].enabled, true); + ASSERT_EQ(plugin_info[1].name, "b"); + ASSERT_EQ(plugin_info[1].enabled, true); + ASSERT_EQ(plugin_info[2].name, "c"); + ASSERT_EQ(plugin_info[2].enabled, true); +} + +// Test calling disable on an already disabled plugin is ok. +TEST_F(PluginManagerTest, SystemRuntimePluginDisableDisabled) { + RegisterMockSystemRuntimePlugins(); + + // Initial call to disable the plugin should succeed. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + + // The second call should also succeed because the plugin is already disabled. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + + // The call to re-enable the plugin should succeed. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true)); + + // The second call should also succeed since the plugin is already enabled. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true)); +} + +// Test calling disable on an already disabled plugin is ok. +TEST_F(PluginManagerTest, SystemRuntimePluginDisableNonExistent) { + RegisterMockSystemRuntimePlugins(); + + // Both enable and disable should return false for a non-existent plugin. + ASSERT_FALSE( + PluginManager::SetSystemRuntimePluginEnabled("does_not_exist", true)); + ASSERT_FALSE( + PluginManager::SetSystemRuntimePluginEnabled("does_not_exist", false)); +} + +// Test disabling all plugins and then re-enabling them in a different +// order will restore the original plugin order. +TEST_F(PluginManagerTest, SystemRuntimePluginDisableAll) { + RegisterMockSystemRuntimePlugins(); + + // Validate initial state of registered plugins. + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginC); + + // Disable all the active plugins. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("a", false)); + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", false)); + + // Should have no active plugins. + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), nullptr); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), nullptr); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr); + + // And show up in the plugin info correctly. + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].enabled, false); + ASSERT_EQ(plugin_info[1].name, "b"); + ASSERT_EQ(plugin_info[1].enabled, false); + ASSERT_EQ(plugin_info[2].name, "c"); + ASSERT_EQ(plugin_info[2].enabled, false); + + // Enable plugins in reverse order and validate expected indicies. + // They should show up in the original plugin order. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", true)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginC); + + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("a", true)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginC); +} + +// Test un-registering a disabled plugin works. +TEST_F(PluginManagerTest, UnRegisterDisabledSystemRuntimePlugin) { + RegisterMockSystemRuntimePlugins(); + + // Initial plugin info has all three registered plugins. + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 3u); + + // First disable a plugin, then unregister it. Both should succeed. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB)); + + // After un-registering a plugin it should be removed from plugin info. + plugin_info = PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(plugin_info.size(), 2u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[0].enabled, true); + ASSERT_EQ(plugin_info[1].name, "c"); + ASSERT_EQ(plugin_info[1].enabled, true); +} + +// Test un-registering and then re-registering a plugin will change the order of +// loaded plugins. +TEST_F(PluginManagerTest, UnRegisterSystemRuntimePluginChangesOrder) { + RegisterMockSystemRuntimePlugins(); + + std::vector plugin_info = + PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginC); + + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[1].name, "b"); + ASSERT_EQ(plugin_info[2].name, "c"); + + // Unregister and then registering a plugin puts it at the end of the order + // list. + ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB)); + ASSERT_TRUE(PluginManager::RegisterPlugin("b", "New test instance B", + CreateSystemRuntimePluginB)); + + // Check the callback indices match as expected. + plugin_info = PluginManager::GetSystemRuntimePluginInfo(); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginB); + + // And plugin info should match as well. + ASSERT_EQ(plugin_info.size(), 3u); + ASSERT_EQ(plugin_info[0].name, "a"); + ASSERT_EQ(plugin_info[1].name, "c"); + ASSERT_EQ(plugin_info[2].name, "b"); + ASSERT_EQ(plugin_info[2].description, "New test instance B"); + + // Disabling and re-enabling the "c" plugin should slot it back + // into the middle of the order. Originally it was last, but after + // un-registering and re-registering "b" it should now stay in + // the middle of the order. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", false)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginB); + + // And re-enabling + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", true)); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), + CreateSystemRuntimePluginA); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), + CreateSystemRuntimePluginC); + ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), + CreateSystemRuntimePluginB); +}