diff --git a/fairmq/CMakeLists.txt b/fairmq/CMakeLists.txt index 526e790d..4eeace90 100644 --- a/fairmq/CMakeLists.txt +++ b/fairmq/CMakeLists.txt @@ -88,6 +88,8 @@ set(FAIRMQ_HEADER_FILES options/FairMQSuboptParser.h options/FairProgOptions.h options/FairProgOptionsHelper.h + Plugin.h + PluginManager.h runFairMQDevice.h shmem/FairMQMessageSHM.h shmem/FairMQPollerSHM.h @@ -141,6 +143,8 @@ set(FAIRMQ_SOURCE_FILES options/FairMQProgOptions.cxx options/FairMQSuboptParser.cxx options/FairProgOptions.cxx + Plugin.cxx + PluginManager.cxx shmem/FairMQMessageSHM.cxx shmem/FairMQPollerSHM.cxx shmem/FairMQSocketSHM.cxx diff --git a/fairmq/Plugin.cxx b/fairmq/Plugin.cxx new file mode 100644 index 00000000..6da7c423 --- /dev/null +++ b/fairmq/Plugin.cxx @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include +#include + +using namespace std; + +fair::mq::Plugin::Plugin(const string name, const Version version, const string maintainer, const string homepage) +: fkName(name) +, fkVersion(version) +, fkMaintainer(maintainer) +, fkHomepage(homepage) +{ + LOG(DEBUG) << "Loaded plugin: " << *this; +} + +fair::mq::Plugin::~Plugin() +{ + LOG(DEBUG) << "Unloaded plugin: " << *this; +} diff --git a/fairmq/Plugin.h b/fairmq/Plugin.h new file mode 100644 index 00000000..2797e566 --- /dev/null +++ b/fairmq/Plugin.h @@ -0,0 +1,91 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#ifndef FAIR_MQ_PLUGIN_H +#define FAIR_MQ_PLUGIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fair +{ +namespace mq +{ + +/** + * @class Plugin Plugin.h + * @brief Base class for FairMQ plugins + * + * The plugin base class encapsulates the plugin metadata. + */ +class Plugin +{ + public: + + struct Version + { + const int fkMajor, fkMinor, fkPatch; + + friend auto operator< (const Version& lhs, const Version& rhs) -> bool { return std::tie(lhs.fkMajor, lhs.fkMinor, lhs.fkPatch) < std::tie(rhs.fkMajor, rhs.fkMinor, rhs.fkPatch); } + friend auto operator> (const Version& lhs, const Version& rhs) -> bool { return rhs < lhs; } + friend auto operator<=(const Version& lhs, const Version& rhs) -> bool { return !(lhs > rhs); } + friend auto operator>=(const Version& lhs, const Version& rhs) -> bool { return !(lhs < rhs); } + friend auto operator==(const Version& lhs, const Version& rhs) -> bool { return std::tie(lhs.fkMajor, lhs.fkMinor, lhs.fkPatch) == std::tie(rhs.fkMajor, rhs.fkMinor, rhs.fkPatch); } + friend auto operator!=(const Version& lhs, const Version& rhs) -> bool { return !(lhs == rhs); } + friend auto operator<<(std::ostream& os, const Version& v) -> std::ostream& { return os << v.fkMajor << "." << v.fkMinor << "." << v.fkPatch; } + }; + + Plugin() = delete; + Plugin(const std::string name, const Version version, const std::string maintainer, const std::string homepage); + virtual ~Plugin(); + + auto GetName() const -> const std::string& { return fkName; } + auto GetVersion() const -> const Version { return fkVersion; } + auto GetMaintainer() const -> const std::string& { return fkMaintainer; } + auto GetHomepage() const -> const std::string& { return fkHomepage; } + + friend auto operator==(const Plugin& lhs, const Plugin& rhs) -> bool { return std::make_tuple(lhs.GetName(), lhs.GetVersion()) == std::make_tuple(rhs.GetName(), rhs.GetVersion()); } + friend auto operator!=(const Plugin& lhs, const Plugin& rhs) -> bool { return !(lhs == rhs); } + friend auto operator<<(std::ostream& os, const Plugin& p) -> std::ostream& + { + return os << "'" << p.GetName() << "', " + << "version '" << p.GetVersion() << "', " + << "maintainer '" << p.GetMaintainer() << "', " + << "homepage '" << p.GetHomepage() << "'"; + } + static auto NoProgramOptions() -> const boost::optional { return boost::none; } + + private: + + const std::string fkName; + const Version fkVersion; + const std::string fkMaintainer; + const std::string fkHomepage; + +}; /* class Plugin */ + +} /* namespace mq */ +} /* namespace fair */ + +#define REGISTER_FAIRMQ_PLUGIN(KLASS, NAME, VERSION, MAINTAINER, HOMEPAGE, PROGOPTIONS) \ +static auto Make_##NAME##_Plugin() -> std::shared_ptr \ +{ \ + return std::make_shared(std::string{#NAME}, VERSION, std::string{MAINTAINER}, std::string{HOMEPAGE}); \ +} \ +BOOST_DLL_ALIAS(Make_##NAME##_Plugin, make_##NAME##_plugin) \ +BOOST_DLL_ALIAS(PROGOPTIONS, get_##NAME##_plugin_progoptions) + +#endif /* FAIR_MQ_PLUGIN_H */ diff --git a/fairmq/PluginManager.cxx b/fairmq/PluginManager.cxx new file mode 100644 index 00000000..a3e5b540 --- /dev/null +++ b/fairmq/PluginManager.cxx @@ -0,0 +1,199 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using fair::mq::tools::ToString; +using fair::mq::tools::ToStrVector; +using fair::mq::Plugin; +namespace fs = boost::filesystem; +namespace po = boost::program_options; +namespace dll = boost::dll; +using boost::optional; + +const std::string fair::mq::PluginManager::fgkLibPrefix = "FairMQPlugin_"; + +fair::mq::PluginManager::PluginManager() +: fSearchPaths({"."}) +{ +} + +auto fair::mq::PluginManager::ValidateSearchPath(const fs::path& path) -> void +{ + if (path.empty()) throw BadSearchPath{"Specified path is empty."}; + // we ignore non-existing search paths + if (fs::exists(path) && !fs::is_directory(path)) throw BadSearchPath{ToString(path, " is not a directory.")}; +} + +auto fair::mq::PluginManager::SetSearchPaths(const vector& searchPaths) -> void +{ + for_each(begin(searchPaths), end(searchPaths), ValidateSearchPath); + fSearchPaths = searchPaths; +} + +auto fair::mq::PluginManager::AppendSearchPath(const fs::path& path) -> void +{ + ValidateSearchPath(path); + fSearchPaths.push_back(path); +} + +auto fair::mq::PluginManager::PrependSearchPath(const fs::path& path) -> void +{ + ValidateSearchPath(path); + fSearchPaths.insert(begin(fSearchPaths), path); +} + +auto fair::mq::PluginManager::ProgramOptions() -> const optional +{ + auto plugin_options = po::options_description{"Plugin Manager"}; + plugin_options.add_options() + ("plugin-search-path,S", po::value>()->multitoken(), + "List of plugin search paths.\n\n" + "* Override default search path, e.g.\n" + " -S /home/user/lib /lib\n" + "* Append(>) or prepend(<) to default search path, e.g.\n" + " -S >/lib /lib >(), + "List of plugin names to load in order, e.g. if the file is called 'libFairMQPlugin_example.so', just list 'example' or 'd:example' here. To load a static plugin, list 's:example' here (The plugin must be already linked into the executable)."); + return plugin_options; +} + +auto fair::mq::PluginManager::MakeFromCommandLineOptions(const vector args) -> shared_ptr +{ + // Parse command line options + auto options = ProgramOptions().value(); + auto vm = po::variables_map{}; + try + { + auto parsed = po::command_line_parser(args).options(options).allow_unregistered().run(); + po::store(parsed, vm); + po::notify(vm); + } catch (const po::error& e) + { + throw ProgramOptionsParseError{ToString("Error occured while parsing the 'Plugin Manager' program options: ", e.what())}; + } + + // Process plugin search paths + auto append = vector{}; + auto prepend = vector{}; + auto searchPaths = vector{}; + if (vm.count("plugin-search-path")) + { + for (const auto& path : vm["plugin-search-path"].as>()) + { + if (path.substr(0, 1) == "<") { prepend.emplace_back(path.substr(1)); } + else if (path.substr(0, 1) == ">") { append.emplace_back(path.substr(1)); } + else { searchPaths.emplace_back(path); } + } + } + + // Create PluginManager with supplied options + auto mgr = make_shared(); + mgr->SetSearchPaths(searchPaths); + for(const auto& path : prepend) { mgr->PrependSearchPath(path); } + for(const auto& path : append) { mgr->AppendSearchPath(path); } + if (vm.count("plugin")) + { + for (const auto& plugin : vm["plugin"].as>()) + { + mgr->LoadPlugin(plugin); + } + } + + // Return the plugin manager and command line options, that have not been recognized. + return mgr; +} + +auto fair::mq::PluginManager::LoadPlugin(const string& pluginName) -> void +{ + if (pluginName.substr(0,2) == "s:") + { + // Mechanism A: static + LoadPluginStatic(pluginName.substr(2)); + } + else if (pluginName.substr(0,2) == "d:") + { + // Mechanism B: dynamic + LoadPluginDynamic(pluginName.substr(2)); + } + else + { + // Mechanism B: dynamic (default) + LoadPluginDynamic(pluginName); + } +} + +auto fair::mq::PluginManager::LoadPluginStatic(const string& pluginName) -> void +{ + // Load symbol + if (fPluginFactories.find(pluginName) == fPluginFactories.end()) + { + try + { + LoadSymbols(pluginName, dll::program_location()); + } + catch (boost::system::system_error& e) + { + throw PluginLoadError(ToString("An error occurred while loading static plugin ", pluginName, ": ", e.what())); + } + } + + InstantiatePlugin(pluginName); +} + +auto fair::mq::PluginManager::LoadPluginDynamic(const string& pluginName) -> void +{ + // Search plugin and load, if found + if (fPluginFactories.find(pluginName) == fPluginFactories.end()) + { + auto success = false; + for(const auto& searchPath : SearchPaths()) + { + try + { + LoadSymbols( + pluginName, + searchPath / ToString(LibPrefix(), pluginName), + dll::load_mode::append_decorations + ); + + success = true; + break; + } + catch (boost::system::system_error& e) + { + if(string{e.what()}.find("No such file or directory") == string::npos) + { + throw PluginLoadError(ToString("An error occurred while loading dynamic plugin ", pluginName, ": ", e.what())); + } + } + } + if(!success) throw PluginLoadError(ToString("The plugin ", pluginName, " could not be found in the plugin search paths.")); + } + + InstantiatePlugin(pluginName); +} + +auto fair::mq::PluginManager::InstantiatePlugin(const string& pluginName) -> void +{ + if (fPlugins.find(pluginName) == fPlugins.end()) + { + fPlugins[pluginName] = fPluginFactories[pluginName](); + fPluginOrder.push_back(pluginName); + } +} diff --git a/fairmq/PluginManager.h b/fairmq/PluginManager.h new file mode 100644 index 00000000..22c7352d --- /dev/null +++ b/fairmq/PluginManager.h @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#ifndef FAIR_MQ_PLUGINMANAGER_H +#define FAIR_MQ_PLUGINMANAGER_H + +#include +#include +#define BOOST_FILESYSTEM_VERSION 3 +#define BOOST_FILESYSTEM_NO_DEPRECATED +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fair +{ +namespace mq +{ + +/** + * @class PluginManager PluginManager.h + * @brief manages and owns plugin instances + * + * The plugin manager is responsible for the whole plugin lifecycle. It + * facilitates three plugin mechanisms: + * A static (built-in) plugins + * B dynamic plugins (shared libraries) + */ +class PluginManager +{ + public: + + using PluginFactory = std::shared_ptr(); + using PluginProgOptions = const boost::optional(); + + PluginManager(); + + auto SetSearchPaths(const std::vector&) -> void; + auto AppendSearchPath(const boost::filesystem::path&) -> void; + auto PrependSearchPath(const boost::filesystem::path&) -> void; + auto SearchPaths() const -> const std::vector& { return fSearchPaths; } + struct BadSearchPath : std::invalid_argument { using std::invalid_argument::invalid_argument; }; + + auto LoadPlugin(const std::string& pluginName) -> void; + auto LoadPlugins(const std::vector& pluginNames) -> void { for(const auto& pluginName : pluginNames) { LoadPlugin(pluginName); } } + struct PluginLoadError : std::runtime_error { using std::runtime_error::runtime_error; }; + + static auto ProgramOptions() -> const boost::optional; + static auto MakeFromCommandLineOptions(const std::vector) -> std::shared_ptr; + struct ProgramOptionsParseError : std::runtime_error { using std::runtime_error::runtime_error; }; + + static auto LibPrefix() -> const std::string& { return fgkLibPrefix; } + + auto ForEachPlugin(std::function func) -> void { for(const auto& p : fPluginOrder) { func(*fPlugins[p]); } } + auto ForEachPluginProgOptions(std::function func) const -> void { for(const auto& pair : fPluginProgOptions) { func(pair.second); } } + + private: + + static auto ValidateSearchPath(const boost::filesystem::path&) -> void; + + auto LoadPluginStatic(const std::string& pluginName) -> void; + auto LoadPluginDynamic(const std::string& pluginName) -> void; + template + auto LoadSymbols(const std::string& pluginName, Args&&... args) -> void + { + using namespace boost::dll; + using fair::mq::tools::ToString; + + auto lib = shared_library{std::forward(args)...}; + + fPluginFactories[pluginName] = import_alias( + shared_library{lib}, + ToString("make_", pluginName, "_plugin") + ); + + try + { + fPluginProgOptions.insert({ + pluginName, + lib.get_alias(ToString("get_", pluginName, "_plugin_progoptions"))().value() + }); + } + catch (const boost::bad_optional_access& e) { /* just ignore, if no prog options are declared */ } + } + + auto InstantiatePlugin(const std::string& pluginName) -> void; + + static const std::string fgkLibPrefix; + std::vector fSearchPaths; + std::map> fPluginFactories; + std::map> fPlugins; + std::vector fPluginOrder; + std::map fPluginProgOptions; + +}; /* class PluginManager */ + +} /* namespace mq */ +} /* namespace fair */ + +#endif /* FAIR_MQ_PLUGINMANAGER_H */ diff --git a/fairmq/Tools.h b/fairmq/Tools.h index b7a872ec..31aa9a38 100644 --- a/fairmq/Tools.h +++ b/fairmq/Tools.h @@ -12,6 +12,7 @@ // IWYU pragma: begin_exports #include #include +#include // IWYU pragma: end_exports #endif // FAIR_MQ_TOOLS_H diff --git a/fairmq/test/CMakeLists.txt b/fairmq/test/CMakeLists.txt index 22ef519f..83191cd0 100644 --- a/fairmq/test/CMakeLists.txt +++ b/fairmq/test/CMakeLists.txt @@ -77,6 +77,58 @@ add_testsuite(FairMQ.Device RUN_SERIAL ON ) +set(VERSION_MAJOR 1) +set(VERSION_MINOR 1) +set(VERSION_PATCH 0) +set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/helper/plugins/dummy.h.in ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy.h) +add_testlib(FairMQPlugin_test_dummy + SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy.h + helper/plugins/dummy.cxx + + LINKS FairMQ + INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins + HIDDEN + VERSION ${VERSION} +) + +set(VERSION_MAJOR 2) +set(VERSION_MINOR 2) +set(VERSION_PATCH 0) +set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/helper/plugins/dummy2.h.in ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h) +add_testlib(FairMQPlugin_test_dummy2 + SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins/dummy2.h + helper/plugins/dummy2.cxx + + LINKS FairMQ + INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/helper/plugins + HIDDEN + VERSION ${VERSION} +) + +add_testsuite(FairMQ.Plugins + SOURCES + plugins/runner.cxx + plugins/_plugin.cxx + plugins/_plugin_manager.cxx + + LINKS FairMQ + DEPENDS FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 + TIMEOUT 10 +) + +add_testsuite(FairMQ.PluginsStatic + SOURCES + plugins/runner.cxx + plugins/_plugin_manager_static.cxx + + LINKS FairMQ FairMQPlugin_test_dummy FairMQPlugin_test_dummy2 + TIMEOUT 10 +) + ############################## # Aggregate all test targets # ############################## diff --git a/fairmq/test/helper/plugins/dummy.cxx b/fairmq/test/helper/plugins/dummy.cxx new file mode 100644 index 00000000..384b5e28 --- /dev/null +++ b/fairmq/test/helper/plugins/dummy.cxx @@ -0,0 +1,9 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include diff --git a/fairmq/test/helper/plugins/dummy.h.in b/fairmq/test/helper/plugins/dummy.h.in new file mode 100644 index 00000000..1f249194 --- /dev/null +++ b/fairmq/test/helper/plugins/dummy.h.in @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#ifndef FAIR_MQ_TEST_PLUGIN_DUMMY +#define FAIR_MQ_TEST_PLUGIN_DUMMY + +#include + +#include +#include +#include + +namespace fair +{ +namespace mq +{ +namespace test +{ + +class DummyPlugin : public fair::mq::Plugin +{ + public: + + DummyPlugin(const std::string name, const Version version, const std::string maintainer, const std::string homepage) + : Plugin(name, version, maintainer, homepage) + { + } + +}; /* class DummyPlugin */ + +auto DummyPluginProgramOptions() -> const boost::optional +{ + using namespace boost::program_options; + using std::string; + + auto plugin_options = options_description{"Dummy Plugin"}; + plugin_options.add_options() + ("custom-dummy-option", value(), "Cool custom option."); + ("custom-dummy-option2", value(), "Another cool custom option."); + return plugin_options; +} + +REGISTER_FAIRMQ_PLUGIN( + DummyPlugin, // Class name + test_dummy, // Plugin name (string, lower case chars only) + (fair::mq::Plugin::Version{@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@}), // Version + "Mr. Dummy ", // Maintainer + "https://git.test.net/dummy.git", // Homepage + fair::mq::test::DummyPluginProgramOptions // Free function which declares custom program options for the plugin + // signature: () -> boost::optional +) + +} /* namespace test */ +} /* namespace mq */ +} /* namespace fair */ + +#endif /* FAIR_MQ_TEST_PLUGIN_DUMMY */ diff --git a/fairmq/test/helper/plugins/dummy2.cxx b/fairmq/test/helper/plugins/dummy2.cxx new file mode 100644 index 00000000..7ae32d00 --- /dev/null +++ b/fairmq/test/helper/plugins/dummy2.cxx @@ -0,0 +1,9 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include diff --git a/fairmq/test/helper/plugins/dummy2.h.in b/fairmq/test/helper/plugins/dummy2.h.in new file mode 100644 index 00000000..3bd47d68 --- /dev/null +++ b/fairmq/test/helper/plugins/dummy2.h.in @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#ifndef FAIR_MQ_TEST_PLUGIN_DUMMY2 +#define FAIR_MQ_TEST_PLUGIN_DUMMY2 + +#include + +namespace fair +{ +namespace mq +{ +namespace test +{ + +class Dummy2Plugin : public fair::mq::Plugin +{ + public: + + Dummy2Plugin(const std::string name, const Version version, const std::string maintainer, const std::string homepage) + : Plugin(name, version, maintainer, homepage) + { + } +}; /* class Dummy2Plugin */ + +REGISTER_FAIRMQ_PLUGIN( + Dummy2Plugin, + test_dummy2, + (Plugin::Version{@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@}), + "Mr. Dummy ", + "https://git.test.net/dummy.git", + fair::mq::Plugin::NoProgramOptions +) + +} /* namespace test */ +} /* namespace mq */ +} /* namespace fair */ + +#endif /* FAIR_MQ_TEST_PLUGIN_DUMMY */ diff --git a/fairmq/test/plugins/_plugin.cxx b/fairmq/test/plugins/_plugin.cxx new file mode 100644 index 00000000..3f2e44a3 --- /dev/null +++ b/fairmq/test/plugins/_plugin.cxx @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include +#include +#include +#include + +namespace +{ + +using namespace std; +using fair::mq::Plugin; + +TEST(Plugin, Operators) +{ + auto p1 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git"}; + auto p2 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git"}; + auto p3 = Plugin{"file", {1, 0, 0}, "Foo Bar ", "https://git.test.net/file.git"}; + EXPECT_EQ(p1, p2); + EXPECT_NE(p1, p3); +} + +TEST(Plugin, OstreamOperators) +{ + auto p1 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git"}; + stringstream ss; + ss << p1; + EXPECT_EQ(ss.str(), string{"'dds', version '1.0.0', maintainer 'Foo Bar ', homepage 'https://git.test.net/dds.git'"}); +} + +TEST(PluginVersion, Operators) +{ + struct Plugin::Version v1{1, 0, 0}; + struct Plugin::Version v2{1, 0, 0}; + struct Plugin::Version v3{1, 2, 0}; + EXPECT_EQ(v1, v2); + EXPECT_NE(v1, v3); + EXPECT_GT(v3, v2); + EXPECT_LT(v1, v3); + EXPECT_GE(v3, v2); + EXPECT_GE(v2, v1); + EXPECT_LE(v1, v2); + EXPECT_LE(v2, v3); +} + +} /* namespace */ diff --git a/fairmq/test/plugins/_plugin_manager.cxx b/fairmq/test/plugins/_plugin_manager.cxx new file mode 100644 index 00000000..08f6c02c --- /dev/null +++ b/fairmq/test/plugins/_plugin_manager.cxx @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include +#include +#include +#include +#include + +namespace +{ + +using namespace fair::mq; +using namespace boost::filesystem; +using namespace boost::program_options; +using namespace std; + +TEST(PluginManager, LoadPlugin) +{ + auto mgr = PluginManager{}; + mgr.PrependSearchPath("./lib"); + + ASSERT_NO_THROW(mgr.LoadPlugin("test_dummy")); + ASSERT_NO_THROW(mgr.LoadPlugin("test_dummy2")); + + // check order + auto expected = vector{"test_dummy", "test_dummy2"}; + auto actual = vector{}; + mgr.ForEachPlugin([&](Plugin& plugin){ actual.push_back(plugin.GetName()); }); + ASSERT_TRUE(actual == expected); + + // program options + auto count = 0; + mgr.ForEachPluginProgOptions([&count](const options_description& d){ ++count; }); + ASSERT_EQ(count, 1); +} + +TEST(PluginManager, Factory) +{ + const auto args = vector{"-l", "debug", "--help", "-S", ">/lib", "{path1, path2, path3, path4}; + ASSERT_TRUE(static_cast(mgr)); + ASSERT_TRUE(mgr->SearchPaths() == expected); +} + +TEST(PluginManager, SearchPathValidation) +{ + const auto path1 = path{"/tmp/test1"}; + const auto path2 = path{"/tmp/test2"}; + const auto path3 = path{"/tmp/test3"}; + auto mgr = PluginManager{}; + + mgr.SetSearchPaths({path1, path2}); + auto expected = vector{path1, path2}; + ASSERT_EQ(mgr.SearchPaths(), expected); + + mgr.AppendSearchPath(path3); + expected = vector{path1, path2, path3}; + ASSERT_EQ(mgr.SearchPaths(), expected); + + mgr.PrependSearchPath(path3); + expected = vector{path3, path1, path2, path3}; + ASSERT_EQ(mgr.SearchPaths(), expected); +} + +TEST(PluginManager, SearchPaths) +{ + const auto temp = temp_directory_path() / unique_path(); + create_directories(temp); + const auto non_existing_dir = temp / "non-existing-dir"; + const auto existing_dir = temp / "existing-dir"; + create_directories(existing_dir); + const auto existing_file = temp / "existing-file.so"; + std::fstream fs; + fs.open(existing_file.string(), std::fstream::out); + fs.close(); + const auto empty_path = path{""}; + + auto mgr = PluginManager{}; + ASSERT_NO_THROW(mgr.AppendSearchPath(non_existing_dir)); + ASSERT_NO_THROW(mgr.AppendSearchPath(existing_dir)); + ASSERT_THROW(mgr.AppendSearchPath(existing_file), PluginManager::BadSearchPath); + ASSERT_NO_THROW(mgr.PrependSearchPath(non_existing_dir)); + ASSERT_NO_THROW(mgr.PrependSearchPath(existing_dir)); + ASSERT_THROW(mgr.PrependSearchPath(existing_file), PluginManager::BadSearchPath); + ASSERT_NO_THROW(mgr.SetSearchPaths({non_existing_dir, existing_dir})); + ASSERT_THROW(mgr.SetSearchPaths({non_existing_dir, existing_file}), PluginManager::BadSearchPath); + ASSERT_THROW(mgr.SetSearchPaths({non_existing_dir, empty_path}), PluginManager::BadSearchPath); + + remove_all(temp); +} + +} /* namespace */ diff --git a/fairmq/test/plugins/_plugin_manager_static.cxx b/fairmq/test/plugins/_plugin_manager_static.cxx new file mode 100644 index 00000000..a842f9dc --- /dev/null +++ b/fairmq/test/plugins/_plugin_manager_static.cxx @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include +#include +#include +#include + +namespace +{ + +using namespace fair::mq; +using namespace std; + +TEST(PluginManager, LoadPluginStatic) +{ + auto mgr = PluginManager{}; + + ASSERT_NO_THROW(mgr.LoadPlugin("s:test_dummy")); + ASSERT_NO_THROW(mgr.LoadPlugin("s:test_dummy2")); +} + +} /* namespace */ diff --git a/fairmq/test/plugins/runner.cxx b/fairmq/test/plugins/runner.cxx new file mode 100644 index 00000000..2357bcc3 --- /dev/null +++ b/fairmq/test/plugins/runner.cxx @@ -0,0 +1,16 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + return RUN_ALL_TESTS(); +} diff --git a/fairmq/tools/FairMQTools.h b/fairmq/tools/FairMQTools.h index 5e21fab8..5d7db60d 100644 --- a/fairmq/tools/FairMQTools.h +++ b/fairmq/tools/FairMQTools.h @@ -5,6 +5,7 @@ #include #include +#include namespace FairMQ { @@ -17,6 +18,8 @@ using fair::mq::tools::getHostIPs; using fair::mq::tools::getInterfaceIP; using fair::mq::tools::getDefaultRouteNetworkInterface; +using fair::mq::tools::S; + } // namespace tools } // namespace FairMQ diff --git a/fairmq/tools/Network.h b/fairmq/tools/Network.h index 9df4cf36..3f1f6145 100644 --- a/fairmq/tools/Network.h +++ b/fairmq/tools/Network.h @@ -36,7 +36,7 @@ namespace tools { // returns a map with network interface names as keys and their IP addresses as values -int getHostIPs(std::map& addressMap) +inline int getHostIPs(std::map& addressMap) { struct ifaddrs *ifaddr, *ifa; int s; @@ -73,7 +73,7 @@ int getHostIPs(std::map& addressMap) } // get IP address of a given interface name -std::string getInterfaceIP(std::string interface) +inline std::string getInterfaceIP(std::string interface) { std::map IPs; getHostIPs(IPs); @@ -89,7 +89,7 @@ std::string getInterfaceIP(std::string interface) } // get name of the default route interface -std::string getDefaultRouteNetworkInterface() +inline std::string getDefaultRouteNetworkInterface() { std::array buffer; std::string interfaceName; diff --git a/fairmq/tools/Strings.h b/fairmq/tools/Strings.h new file mode 100644 index 00000000..435f26d0 --- /dev/null +++ b/fairmq/tools/Strings.h @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * + * * + * This software is distributed under the terms of the * + * GNU Lesser General Public Licence (LGPL) version 3, * + * copied verbatim in the file "LICENSE" * + ********************************************************************************/ + +#ifndef FAIR_MQ_TOOLS_STRINGS_H +#define FAIR_MQ_TOOLS_STRINGS_H + +#include +#include +#include +#include + +namespace fair +{ +namespace mq +{ +namespace tools +{ + +/// @brief concatenates a variable number of args with the << operator via a stringstream +/// @param t objects to be concatenated +/// @return concatenated string +template +auto ToString(T&&... t) -> std::string +{ + std::stringstream ss; + (void)std::initializer_list{(ss << t, 0)...}; + return ss.str(); +} + +/// @brief convert command line arguments from main function to vector of strings +inline auto ToStrVector(const int argc, const char* argv[], const bool dropProgramName = true) -> std::vector +{ + auto res = std::vector{}; + if (dropProgramName) + { + res.assign(argv + 1, argv + argc); + } else + { + res.assign(argv, argv + argc); + } + return res; +} + +} /* namespace tools */ +} /* namespace mq */ +} /* namespace fair */ + +#endif /* FAIR_MQ_TOOLS_STRINGS_H */