llvm-project/lldb/unittests/Core/PluginManagerTest.cpp
David Peixotto d4fe522eb4
Add commands to list/enable/disable plugins (#134418)
This commit adds three new commands for managing plugins. The `list`
command will show which plugins are currently registered and their
enabled state. The `enable` and `disable` commands can be used to enable
or disable plugins.

A disabled plugin will not show up to the PluginManager when it iterates
over available plugins of a particular type.

The purpose of these commands is to provide more visibility into
registered plugins and allow users to disable plugins for experimental
perf reasons.

There are a few limitations to the current implementation

1. Only SystemRuntime and InstrumentationRuntime plugins are currently
supported. We can easily extend the existing implementation to support
more types. The scope was limited to these plugins to keep the PR size
manageable.

2. Only "statically" know plugin types are supported (i.e. those managed
by the PluginManager and not from `plugin load`). It is possibly we
could support dynamic plugins as well, but I have not looked into it
yet.
2025-06-09 13:30:13 -07:00

472 lines
19 KiB
C++

#include "lldb/Core/PluginManager.h"
#include "gtest/gtest.h"
using namespace lldb;
using namespace lldb_private;
// Mock system runtime plugin create functions.
// Make them all return different values to avoid the ICF optimization
// from combining them into the same function. The values returned
// are not valid SystemRuntime pointers, but they are unique and
// sufficient for testing.
SystemRuntime *CreateSystemRuntimePluginA(Process *process) {
return (SystemRuntime *)0x1;
}
SystemRuntime *CreateSystemRuntimePluginB(Process *process) {
return (SystemRuntime *)0x2;
}
SystemRuntime *CreateSystemRuntimePluginC(Process *process) {
return (SystemRuntime *)0x3;
}
// 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() {
// Make sure the create functions all have different addresses.
ASSERT_NE(CreateSystemRuntimePluginA, CreateSystemRuntimePluginB);
ASSERT_NE(CreateSystemRuntimePluginB, CreateSystemRuntimePluginC);
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<SystemRuntimeCreateInstance> 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<SystemRuntimeCreateInstance> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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<RegisteredPluginInfo> 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);
}
TEST_F(PluginManagerTest, MatchPluginName) {
PluginNamespace Foo{"foo", nullptr, nullptr};
RegisteredPluginInfo Bar{"bar", "bar plugin ", true};
RegisteredPluginInfo Baz{"baz", "baz plugin ", true};
// Empty pattern matches everything.
ASSERT_TRUE(PluginManager::MatchPluginName("", Foo, Bar));
// Plugin namespace matches all plugins in that namespace.
ASSERT_TRUE(PluginManager::MatchPluginName("foo", Foo, Bar));
ASSERT_TRUE(PluginManager::MatchPluginName("foo", Foo, Baz));
// Fully qualified plugin name matches only that plugin.
ASSERT_TRUE(PluginManager::MatchPluginName("foo.bar", Foo, Bar));
ASSERT_FALSE(PluginManager::MatchPluginName("foo.baz", Foo, Bar));
// Prefix match should not match.
ASSERT_FALSE(PluginManager::MatchPluginName("f", Foo, Bar));
ASSERT_FALSE(PluginManager::MatchPluginName("foo.", Foo, Bar));
ASSERT_FALSE(PluginManager::MatchPluginName("foo.ba", Foo, Bar));
}
TEST_F(PluginManagerTest, JsonFormat) {
RegisterMockSystemRuntimePlugins();
// We expect the following JSON output:
// {
// "system-runtime": [
// {
// "enabled": true,
// "name": "a"
// },
// {
// "enabled": true,
// "name": "b"
// },
// {
// "enabled": true,
// "name": "c"
// }
// ]
// }
llvm::json::Object obj = PluginManager::GetJSON();
// We should have a "system-runtime" array in the top-level object.
llvm::json::Array *maybe_array = obj.getArray("system-runtime");
ASSERT_TRUE(maybe_array != nullptr);
auto &array = *maybe_array;
ASSERT_EQ(array.size(), 3u);
// Check plugin "a" info.
ASSERT_TRUE(array[0].getAsObject() != nullptr);
ASSERT_TRUE(array[0].getAsObject()->getString("name") == "a");
ASSERT_TRUE(array[0].getAsObject()->getBoolean("enabled") == true);
// Check plugin "b" info.
ASSERT_TRUE(array[1].getAsObject() != nullptr);
ASSERT_TRUE(array[1].getAsObject()->getString("name") == "b");
ASSERT_TRUE(array[1].getAsObject()->getBoolean("enabled") == true);
// Check plugin "c" info.
ASSERT_TRUE(array[2].getAsObject() != nullptr);
ASSERT_TRUE(array[2].getAsObject()->getString("name") == "c");
ASSERT_TRUE(array[2].getAsObject()->getBoolean("enabled") == true);
// Disabling a plugin should be reflected in the JSON output.
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
array = *PluginManager::GetJSON().getArray("system-runtime");
ASSERT_TRUE(array[0].getAsObject()->getBoolean("enabled") == true);
ASSERT_TRUE(array[1].getAsObject()->getBoolean("enabled") == false);
ASSERT_TRUE(array[2].getAsObject()->getBoolean("enabled") == true);
// Un-registering a plugin should be reflected in the JSON output.
ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
array = *PluginManager::GetJSON().getArray("system-runtime");
ASSERT_EQ(array.size(), 2u);
ASSERT_TRUE(array[0].getAsObject()->getString("name") == "a");
ASSERT_TRUE(array[1].getAsObject()->getString("name") == "c");
// Filtering the JSON output should only include the matching plugins.
array =
*PluginManager::GetJSON("system-runtime.c").getArray("system-runtime");
ASSERT_EQ(array.size(), 1u);
ASSERT_TRUE(array[0].getAsObject()->getString("name") == "c");
// Empty JSON output is allowed if there are no matching plugins.
obj = PluginManager::GetJSON("non-existent-plugin");
ASSERT_TRUE(obj.empty());
}