Files
llvm-project/lldb/unittests/Core/PluginManagerTest.cpp
Dan Liew 3a50cfeba5 [LLDB][Part 1] Support enabling/disabling InstrumentationRuntime plugins in an debug session (#193328)
This patch is the first part in a patch series that will allow
enabling/disabling InstrumentationRuntime plugins in a running debug
session.

This part adds the `--domain` flag to the `enable`, `disable`, `list`
sub commands of `plugin` shell command and plumbs the value of this flag
to where it will be needed in a subsequent patch. From the user
perspective the flag does nothing useful yet because all values passed
to the flag except `global` (the default and what represents LLDB's
existing behavior) are rejected. Subsequent patches will allow the flag
to do something useful.

The `--domain` flag adds a notion of "domain" to plugins with respect to
their enablement. Previously all plugins were treated as global and have
their enablement stored globally. This is despite the fact that some
plugins clearly are not global. For example the
`instrumentation-runtime` plugins clearly exist on a per-target basis
(the instances of the `InstrumentationRuntime` exist in each process).
In addition to this plugins being "global" means instances of the
`Debugger` instance are not properly isolated from each other. This PR
is a stepping stone towards fixing these design problems. The PR
introduces three different domains for plugins:

* `global` - Enablement of the plugin can be controlled globally. This
is the existing behavior of all LLDB plugins.
* `debugger` - Enablement of the plugin can be controlled on a per
`Debugger` basis.
* `target` - Enablement of the plugin can be controlled on a per
`Target` basis.

These values are encoded in the new `PluginDomainKind` enum.

It is important to note that the design in this PR means a plugin can
support more than one domain. In particular in future patches when
`instrumentation-runtime` plugins gain support for more than just the
`global` domain they will support the `debugger` and `target` domain as
well. The key reason that the `instrumentation-runtime` plugins need to
support more than one domain is that the plugins need a default
enablement value **before** the target exists. That default value will
need to come from the `global` domain. Architecturally it should
probably come from the `debugger` domain instead but refactoring
enablement into Debugger instances is much too large a refactor for this
patch series and is a problem that can be tackled later.

This patch modifies the `PluginNamespace` struct to:

* Store the set of domains supported by the namespace and provided some
helper methods to determine what is supported.
* Store one of two callbacks. Either `SetPluginEnabledGlobalDomain` (the
existing function interface used by most plugins) or
`SetPluginEnabledAllDomains` (a new interface used by
`InstrumentationRuntime` plugins).

In this patch the `InstrumentationRuntime` plugins use the new
`SetPluginEnabledAllDomains` function interface for enablement (i.e. the
interface of `PluginManager::SetInstrumentationRuntimePluginEnabled` has
changed) which passes the `Debugger` instance that made the request and
the domain the user provided to the `plugin enable` or `plugin disable`
command.

To make this patch easier to review the
`PluginManager::SetInstrumentationRuntimePluginEnabled` function
actually rejects all domains except `global` to keep the behavior change
down to a minimum. Proper support for enabling/disabling
instrumentation-runtime plugins in the `target`, and `debugger` domains
will be implemented in a subsequent patch.

The `plugin list` command implementations also reject any domain that
isn't `global`. Support for other domains will be added in the
subsequent patch that adds support for other domains in the
`instrumentation-runtime` plugins.

The `plugin enable`, `plugin disable`, `plugin list` commands will use
the `global` domain by default so that there is no behavior change for
existing workflows.

Two new shell tests are included that exercise the new code paths:

* `command-plugin-enable-disable-domain-flag.test` validates that
`--domain global` works for both global-only and multi-domain plugin
namespaces, and that `--domain debugger` and `--domain target` are
correctly rejected for now.

* `command-plugin-list-domain-flag.test` validates the same behavior for
the list command in both text and JSON output modes.

I am not experienced at adding flags to LLDB shell commands so I had
Claude Code write that part and also help write test cases.

Assisted-by: Claude Code

rdar://167725878
2026-04-30 13:26:24 -07:00

473 lines
18 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:
llvm::SmallVector<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 GetSystemRuntimeCreateCallbacks()
// api.
for (const RegisteredPluginInfo &PluginInfo :
PluginManager::GetSystemRuntimePluginInfo()) {
PluginManager::SetSystemRuntimePluginEnabled(PluginInfo.name, true);
}
// Get a handle to the create call backs for all the registered plugins.
llvm::SmallVector<SystemRuntimeCreateInstance> registered_plugin_callbacks =
PluginManager::GetSystemRuntimeCreateCallbacks();
// 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();
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
ASSERT_EQ(callbacks[2], CreateSystemRuntimePluginC);
}
// Test basic un-register functionality.
TEST_F(PluginManagerTest, UnRegisterSystemRuntimePlugin) {
RegisterMockSystemRuntimePlugins();
ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 2u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
}
// Test registered plugin info functionality.
TEST_F(PluginManagerTest, SystemRuntimePluginInfo) {
RegisterMockSystemRuntimePlugins();
llvm::SmallVector<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.
llvm::SmallVector<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.
llvm::SmallVector<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.
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 2u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
}
// Test plugin disable and enable functionality.
TEST_F(PluginManagerTest, SystemRuntimePluginDisableThenEnable) {
RegisterMockSystemRuntimePlugins();
// Initially plugin b is available in slot 1.
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
}
// Disabling it will remove it from available plugins.
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 2u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
}
// We can re-enable the plugin later and it should go back to the original
// slot.
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
ASSERT_EQ(callbacks[2], CreateSystemRuntimePluginC);
}
// And show up in the plugin info correctly.
llvm::SmallVector<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.
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
ASSERT_EQ(callbacks[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::GetSystemRuntimeCreateCallbacks().size(), 0u);
// And show up in the plugin info correctly.
llvm::SmallVector<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));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 1u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginC);
}
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("a", true));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 2u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
}
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
ASSERT_EQ(callbacks[2], CreateSystemRuntimePluginC);
}
}
// Test un-registering a disabled plugin works.
TEST_F(PluginManagerTest, UnRegisterDisabledSystemRuntimePlugin) {
RegisterMockSystemRuntimePlugins();
// Initial plugin info has all three registered plugins.
llvm::SmallVector<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();
llvm::SmallVector<RegisteredPluginInfo> plugin_info =
PluginManager::GetSystemRuntimePluginInfo();
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
ASSERT_EQ(callbacks[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();
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
ASSERT_EQ(callbacks[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));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 2u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginB);
}
// And re-enabling
ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", true));
{
auto callbacks = PluginManager::GetSystemRuntimeCreateCallbacks();
ASSERT_EQ(callbacks.size(), 3u);
ASSERT_EQ(callbacks[0], CreateSystemRuntimePluginA);
ASSERT_EQ(callbacks[1], CreateSystemRuntimePluginC);
ASSERT_EQ(callbacks[2], CreateSystemRuntimePluginB);
}
}
TEST_F(PluginManagerTest, MatchPluginName) {
auto TmpFn = [](llvm::StringRef, bool) -> bool { return true; };
PluginNamespace Foo{"foo", nullptr, TmpFn};
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());
}