osquery-1/osquery/registry/tests/registry_tests.cpp
2015-04-27 09:40:31 -07:00

273 lines
9.0 KiB
C++

/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <gtest/gtest.h>
#include <osquery/logger.h>
#include <osquery/registry.h>
namespace osquery {
class RegistryTests : public testing::Test {};
class CatPlugin : public Plugin {
public:
CatPlugin() : some_value_(0) {}
protected:
int some_value_;
};
class HouseCat : public CatPlugin {
public:
Status setUp() {
// Make sure the Plugin implementation's init is called.
some_value_ = 9000;
return Status(0, "OK");
}
};
/// This is a manual registry type without a name, so we cannot broadcast
/// this registry type and it does NOT need to conform to a registry API.
class CatRegistry : public RegistryHelper<CatPlugin> {};
TEST_F(RegistryTests, test_registry) {
CatRegistry cats;
/// Add a CatRegistry item (a plugin) called "house".
cats.add<HouseCat>("house");
EXPECT_EQ(cats.count(), 1);
/// Try to add the same plugin with the same name, this is meaningless.
cats.add<HouseCat>("house");
/// Now add the same plugin with a different name, a new plugin instance
/// will be created and registered.
cats.add<HouseCat>("house2");
EXPECT_EQ(cats.count(), 2);
/// Request a plugin to call an API method.
auto cat = cats.get("house");
cats.setUp();
/// Now let's iterate over every registered Cat plugin.
EXPECT_EQ(cats.all().size(), 2);
}
/// Normally we have "Registry" that dictates the set of possible API methods
/// for all registry types. Here we use a "TestRegistry" instead.
class TestCoreRegistry : public RegistryFactory {};
/// We can automatically create a registry type as long as that type conforms
/// to the registry API defined in the "Registry". Here we use "TestRegistry".
/// The above "CatRegistry" was easier to understand, but using a auto
/// registry via the registry create method, we can assign a tracked name
/// and then broadcast that registry name to other plugins.
auto AutoCatRegistry = TestCoreRegistry::create<CatPlugin>("cat");
TEST_F(RegistryTests, test_auto_factory) {
/// Using the registry, and a registry type by name, we can register a
/// plugin HouseCat called "house" like above.
TestCoreRegistry::registry("cat")->add<HouseCat>("auto_house");
TestCoreRegistry::add<HouseCat>("cat", "auto_house2");
TestCoreRegistry::registry("cat")->setUp();
/// When acting on registries by name we can check the broadcasted
/// registry name of other plugin processes (via Thrift) as well as
/// internally registered plugins like HouseCat.
EXPECT_EQ(TestCoreRegistry::registry("cat")->count(), 2);
EXPECT_EQ(TestCoreRegistry::count("cat"), 2);
/// And we can call an API method, since we guarantee CatPlugins conform
/// to the "TestCoreRegistry"'s "TestPluginAPI".
auto cat = TestCoreRegistry::get("cat", "auto_house");
auto same_cat = TestCoreRegistry::get("cat", "auto_house");
EXPECT_EQ(cat, same_cat);
}
class DogPlugin : public Plugin {
public:
DogPlugin() : some_value_(10000) {}
protected:
int some_value_;
};
class Doge : public DogPlugin {
public:
Doge() { some_value_ = 100000; }
};
class BadDoge : public DogPlugin {
public:
Status setUp() { return Status(1, "Expect error... this is a bad dog"); }
};
auto AutoDogRegistry = TestCoreRegistry::create<DogPlugin>("dog", true);
TEST_F(RegistryTests, test_auto_registries) {
TestCoreRegistry::add<Doge>("dog", "doge");
TestCoreRegistry::registry("dog")->setUp();
EXPECT_EQ(TestCoreRegistry::count("dog"), 1);
}
TEST_F(RegistryTests, test_persistant_registries) {
EXPECT_EQ(TestCoreRegistry::count("cat"), 2);
}
TEST_F(RegistryTests, test_registry_exceptions) {
EXPECT_TRUE(TestCoreRegistry::add<Doge>("dog", "duplicate_dog").ok());
// Bad dog will be added fine, but when setup is run, it will be removed.
EXPECT_TRUE(TestCoreRegistry::add<BadDoge>("dog", "bad_doge").ok());
TestCoreRegistry::registry("dog")->setUp();
// Make sure bad dog does not exist.
EXPECT_FALSE(TestCoreRegistry::exists("dog", "bad_doge"));
EXPECT_EQ(TestCoreRegistry::count("dog"), 2);
int exception_count = 0;
try {
TestCoreRegistry::registry("does_not_exist");
} catch (const std::out_of_range& e) {
exception_count++;
}
try {
TestCoreRegistry::add<HouseCat>("does_not_exist", "cat");
} catch (const std::out_of_range& e) {
exception_count++;
}
EXPECT_EQ(exception_count, 2);
}
class WidgetPlugin : public Plugin {
public:
/// The route information will usually be provided by the plugin type.
/// The plugin/registry item will set some structures for the plugin
/// to parse and format. BUT a plugin/registry item can also fill this
/// information in if the plugin type/registry type exposes routeInfo as
/// a virtual method.
PluginResponse routeInfo() const {
PluginResponse info;
info.push_back({{"name", name_}});
return info;
}
/// Plugin types should contain generic request/response formatters and
/// decorators.
std::string secretPower(const PluginRequest& request) const {
if (request.count("secret_power") > 0) {
return request.at("secret_power");
}
return "no_secret_power";
}
};
class SpecialWidget : public WidgetPlugin {
public:
Status call(const PluginRequest& request, PluginResponse& response);
};
Status SpecialWidget::call(const PluginRequest& request,
PluginResponse& response) {
response.push_back(request);
response[0]["from"] = name_;
response[0]["secret_power"] = secretPower(request);
return Status(0, "OK");
}
#define UNUSED(x) (void)(x)
TEST_F(RegistryTests, test_registry_api) {
auto AutoWidgetRegistry = TestCoreRegistry::create<WidgetPlugin>("widgets");
UNUSED(AutoWidgetRegistry);
TestCoreRegistry::add<SpecialWidget>("widgets", "special");
// Test route info propogation, from item to registry, to broadcast.
auto ri = TestCoreRegistry::get("widgets", "special")->routeInfo();
EXPECT_EQ(ri[0].at("name"), "special");
auto rr = TestCoreRegistry::registry("widgets")->getRoutes();
EXPECT_EQ(rr.size(), 1);
EXPECT_EQ(rr.at("special")[0].at("name"), "special");
// Broadcast will include all registries, and all their items.
auto broadcast_info = TestCoreRegistry::getBroadcast();
EXPECT_TRUE(broadcast_info.size() >= 3);
EXPECT_EQ(broadcast_info.at("widgets").at("special")[0].at("name"),
"special");
PluginResponse response;
PluginRequest request;
auto status = TestCoreRegistry::call("widgets", "special", request, response);
EXPECT_TRUE(status.ok());
EXPECT_EQ(response[0].at("from"), "special");
EXPECT_EQ(response[0].at("secret_power"), "no_secret_power");
request["secret_power"] = "magic";
status = TestCoreRegistry::call("widgets", "special", request, response);
EXPECT_EQ(response[0].at("secret_power"), "magic");
}
TEST_F(RegistryTests, test_real_registry) {
EXPECT_TRUE(Registry::count() > 0);
bool has_one_registered = false;
for (const auto& registry : Registry::all()) {
if (Registry::count(registry.first) > 0) {
has_one_registered = true;
break;
}
}
EXPECT_TRUE(has_one_registered);
}
TEST_F(RegistryTests, test_registry_modules) {
// Test the registry's module loading state tracking.
RegistryFactory::locked(false);
EXPECT_FALSE(RegistryFactory::locked());
RegistryFactory::locked(true);
EXPECT_TRUE(RegistryFactory::locked());
RegistryFactory::locked(false);
// Test initializing a module load and the module's registry modifications.
EXPECT_EQ(RegistryFactory::getModule(), 0);
RegistryFactory::initModule("/my/test/module");
// The registry is locked, no modifications during module global ctors.
EXPECT_TRUE(RegistryFactory::locked());
// The 'is the registry using a module' is not set during module ctors.
EXPECT_FALSE(RegistryFactory::usingModule());
EXPECT_EQ(RegistryFactory::getModules().size(), 1);
// The unittest can introspect into the current module.
auto& module = RegistryFactory::getModules().at(RegistryFactory::getModule());
EXPECT_EQ(module.path, "/my/test/module");
EXPECT_EQ(module.name, "");
RegistryFactory::declareModule("test", "0.1.1", "0.0.0", "0.0.1");
// The registry is unlocked after the module is declared.
// This assures that module modifications happen with the correct information
// and state tracking (aka the SDK limits, name, and version).
EXPECT_FALSE(RegistryFactory::locked());
// Now the 'is the registry using a module' is set for the duration of the
// modules loading.
EXPECT_TRUE(RegistryFactory::usingModule());
EXPECT_EQ(module.name, "test");
EXPECT_EQ(module.version, "0.1.1");
EXPECT_EQ(module.sdk_version, "0.0.1");
// Finally, when the module load is complete, we clear state.
RegistryFactory::shutdownModule();
// The registry is again locked.
EXPECT_TRUE(RegistryFactory::locked());
// And the registry is no longer using a module.
EXPECT_FALSE(RegistryFactory::usingModule());
EXPECT_EQ(RegistryFactory::getModule(), 0);
}
}