changeset 500:458e34f16c1f

Irccd: rework native plugins, closes #707
author David Demelier <markand@malikania.fr>
date Mon, 02 Oct 2017 13:05:30 +0200
parents 1c96d1fee4b4
children 111e89a0c541
files cmake/function/IrccdDefineTest.cmake libirccd/CMakeLists.txt libirccd/irccd/dynlib.hpp libirccd/irccd/dynlib_plugin.cpp libirccd/irccd/dynlib_plugin.hpp libirccd/irccd/plugin-dynlib.cpp libirccd/irccd/plugin-dynlib.hpp tests/CMakeLists.txt tests/dynlib_plugin/CMakeLists.txt tests/dynlib_plugin/main.cpp tests/dynlib_plugin/test_plugin.cpp
diffstat 11 files changed, 781 insertions(+), 751 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/function/IrccdDefineTest.cmake	Mon Oct 02 13:04:17 2017 +0200
+++ b/cmake/function/IrccdDefineTest.cmake	Mon Oct 02 13:05:30 2017 +0200
@@ -70,6 +70,7 @@
             ${TEST_FLAGS}
             BOOST_TEST_DYN_LINK
             CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
+            CMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}"
             CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
             SOURCEDIR="${CMAKE_CURRENT_SOURCE_DIR}"
             BINARYDIR="${CMAKE_CURRENT_BINARY_DIR}"
--- a/libirccd/CMakeLists.txt	Mon Oct 02 13:04:17 2017 +0200
+++ b/libirccd/CMakeLists.txt	Mon Oct 02 13:05:30 2017 +0200
@@ -22,6 +22,7 @@
     HEADERS
     ${libirccd_SOURCE_DIR}/irccd/command.hpp
     ${libirccd_SOURCE_DIR}/irccd/config.hpp
+    ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.hpp
     ${libirccd_SOURCE_DIR}/irccd/plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/rule.hpp
@@ -34,6 +35,7 @@
     SOURCES
     ${libirccd_SOURCE_DIR}/irccd/command.cpp
     ${libirccd_SOURCE_DIR}/irccd/config.cpp
+    ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.cpp
     ${libirccd_SOURCE_DIR}/irccd/plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/rule.cpp
@@ -48,7 +50,10 @@
         ${libirccd_SOURCE_DIR}/CMakeLists.txt
         ${HEADERS}
         ${SOURCES}
-    LIBRARIES extern-ircclient libcommon
+    LIBRARIES
+        $<$<BOOL:${IRCCD_SYSTEM_LINUX}>:dl>
+        extern-ircclient
+        libcommon
     PUBLIC_INCLUDES
         $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}/irccd>
         $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}>
--- a/libirccd/irccd/dynlib.hpp	Mon Oct 02 13:04:17 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,337 +0,0 @@
-/*
- * dynlib.hpp -- portable shared library loader
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_DYNLIB_HPP
-#define IRCCD_DYNLIB_HPP
-
-/**
- * \file dynlib.hpp
- * \brief Portable shared library loader.
- * \author David Demelier <markand@malikania.fr>
- */
-
-/**
- * \page Dynlib Dynlib
- * \brief Portable shared library loader.
- *
- * The dynlib module let you open shared libraries dynamically at runtime.
- *
- * ## Operating system support
- *
- * | System  | Support | Remarks            |
- * |---------|---------|--------------------|
- * | Apple   | Ok      |                    |
- * | FreeBSD | Ok      |                    |
- * | Linux   | Ok      | Needs -ldl library |
- * | Windows | Ok      |                    |
- *
- * ## How to export symbols
- *
- * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if
- * not they will be [mangled][name-mangling].
- *
- * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function
- * overloading.
- *
- * Example of **plugin.cpp**:
- *
- * ````cpp
- * #include <iostream>
- *
- * #include "dynlib.hpp"
- *
- * extern "C" {
- *
- * DYNLIB_EXPORT void plugin_load()
- * {
- *   std::cout << "Loading plugin" << std::endl;
- * }
- *
- * DYNLIB_EXPORT void plugin_unload()
- * {
- *   std::cout << "Unloading plugin" << std::endl;
- * }
- *
- * }
- * ````
- *
- * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always
- * add it before any function.
- *
- * To compile, see your compiler documentation or build system. For gcc you can use the following:
- *
- * ````
- * gcc -std=c++14 -shared plugin.cpp -o plugin.so
- * ````
- *
- * ## How to load the library
- *
- * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure
- * that the library can be found. Otherwise, just use an absolute path to the file.
- *
- * ````cpp
- * #include <iostream>
- *
- * #include "dynlib.hpp"
- *
- * int main()
- * {
- *   try {
- *     Dynlib dso("./plugin" DYNLIB_SUFFIX);
- *   } catch (const std::exception &ex) {
- *     std::cerr << ex.what() << std::endl;
- *   }
- *
- *   return 0;
- * }
- * ````
- *
- * ## How to load symbol
- *
- * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function.
- *
- * ````cpp
- * #include <iostream>
- *
- * #include "dynlib.hpp"
- *
- * using PluginLoad = void (*)();
- * using PluginUnload = void (*)();
- *
- * int main()
- * {
- *    try {
- *        Dynlib dso("./plugin" DYNLIB_SUFFIX);
- *
- *        dso.sym<PluginLoad>("plugin_load")();
- *        dso.sym<PluginUnload>("plugin_unload")();
- *    } catch (const std::exception &ex) {
- *        std::cerr << ex.what() << std::endl;
- *    }
- *
- *    return 0;
- * }
- * ````
- *
- * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling
- */
-
-#include <stdexcept>
-#include <string>
-
-#if defined(_WIN32)
-#  include <windows.h>
-#else
-#  include <dlfcn.h>
-#endif
-
-/**
- * \brief Export the symbol.
- *
- * This is required on some platforms and you should put it before your function signature.
- *
- * \code{.cpp}
- * extern "C" {
- *
- * DYNLIB_EXPORT void my_function()
- * {
- * }
- *
- * }
- * \endcode
- */
-#if defined(_WIN32)
-#  define DYNLIB_EXPORT    __declspec(dllexport)
-#else
-#  define DYNLIB_EXPORT
-#endif
-
-/**
- * \brief Usual suffix for the library.
- *
- * This macro expands to the suffix convention for this platform.
- *
- * \code{.cpp}
- * Dynlib library("./myplugin" DYNLIB_SUFFIX);
- * \endcode
- *
- * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong.
- */
-#if defined(_WIN32)
-#  define DYNLIB_SUFFIX ".dll"
-#elif defined(__APPLE__)
-#  define DYNLIB_SUFFIX ".dylib"
-#else
-#  define DYNLIB_SUFFIX ".so"
-#endif
-
-namespace irccd {
-
-/**
- * \brief Load a dynamic module.
- *
- * This class is a portable wrapper to load shared libraries on supported systems.
- */
-class Dynlib {
-private:
-#if defined(_WIN32)
-    using Handle    = HMODULE;
-    using Sym    = FARPROC;
-#else
-    using Handle    = void *;
-    using Sym    = void *;
-#endif
-
-public:
-    /**
-     * \brief Policy for symbol resolution.
-     */
-    enum Policy {
-        Immediately,        //!< load symbols immediately
-        Lazy            //!< load symbols when needed
-    };
-
-private:
-    Handle    m_handle;
-
-    Dynlib(const Dynlib &) = delete;
-    Dynlib &operator=(const Dynlib &) = delete;
-
-    Dynlib(Dynlib &&) = delete;
-    Dynlib &operator=(Dynlib &&) = delete;
-
-#if defined(_WIN32)
-    std::string error()
-    {
-        LPSTR error = nullptr;
-        std::string errmsg;
-
-        FormatMessageA(
-            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
-            NULL,
-            GetLastError(),
-            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-            (LPSTR)&error, 0, NULL);
-
-        if (error) {
-            errmsg = std::string(error);
-            LocalFree(error);
-        }
-
-        return errmsg;
-    }
-#endif
-
-public:
-    /**
-     * Constructor to load a shared module.
-     *
-     * \param path the absolute path
-     * \param policy the policy to load
-     * \throw std::runtime_error on error
-     */
-    inline Dynlib(const std::string &path, Policy policy = Immediately);
-
-    /**
-     * Close the library automatically.
-     */
-    inline ~Dynlib();
-
-    /**
-     * Get a symbol from the library.
-     *
-     * On some platforms the symbol must be manually exported.
-     *
-     * \param name the symbol
-     * \return the symbol
-     * \throw std::runtime_error on error
-     * \see DYNLIB_EXPORT
-     */
-    template <typename T>
-    inline T sym(const std::string &name);
-};
-
-#if defined(_WIN32)
-
-/*
- * Windows implementation
- * ------------------------------------------------------------------
- */
-
-Dynlib::Dynlib(const std::string &path, Policy)
-{
-    m_handle = LoadLibraryA(path.c_str());
-
-    if (m_handle == nullptr)
-        throw std::runtime_error(error());
-}
-
-Dynlib::~Dynlib()
-{
-    FreeLibrary(m_handle);
-    m_handle = nullptr;
-}
-
-template <typename T>
-T Dynlib::sym(const std::string &name)
-{
-    Sym sym = GetProcAddress(m_handle, name.c_str());
-
-    if (sym == nullptr)
-        throw std::runtime_error(error());
-
-    return reinterpret_cast<T>(sym);
-}
-
-#else
-
-/*
- * Unix implementation
- * ------------------------------------------------------------------
- */
-
-Dynlib::Dynlib(const std::string &path, Policy policy)
-{
-    m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY);
-
-    if (m_handle == nullptr)
-        throw std::runtime_error(dlerror());
-}
-
-Dynlib::~Dynlib()
-{
-    dlclose(m_handle);
-    m_handle = nullptr;
-}
-
-template <typename T>
-T Dynlib::sym(const std::string &name)
-{
-    Sym sym = dlsym(m_handle, name.c_str());
-
-    if (sym == nullptr)
-        throw std::runtime_error(dlerror());
-
-    return reinterpret_cast<T>(sym);
-}
-
-#endif
-
-#endif // !IRCCD_DYNLIB_HPP
-
-} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/dynlib_plugin.cpp	Mon Oct 02 13:05:30 2017 +0200
@@ -0,0 +1,184 @@
+/*
+ * dynlib_plugin.cpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cctype>
+#include <algorithm>
+
+#include <boost/filesystem.hpp>
+
+#include "dynlib_plugin.hpp"
+#include "util.hpp"
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+#   define DYNLIB_EXTENSION ".dll"
+#elif defined(IRCCD_SYSTEM_MAC)
+#   define DYNLIB_EXTENSION ".dylib"
+#else
+#   define DYNLIB_EXTENSION ".so"
+#endif
+
+namespace irccd {
+
+dynlib_plugin::dynlib_plugin(std::string name, std::string path)
+    : plugin(name, path)
+    , dso_(path)
+{
+    using load_t = std::unique_ptr<plugin>(std::string, std::string);
+
+    /*
+     * Function name is determined from the plugin filename where all non
+     * alphabetic characters are removed.
+     *
+     * Example: foo_bar-baz___.so becomes irccd_foobarbaz_load.
+     */
+    auto base = boost::filesystem::path(path).stem().string();
+    auto need_remove = [] (auto c) {
+        return !std::isalnum(c);
+    };
+
+    base.erase(std::remove_if(base.begin(), base.end(), need_remove), base.end());
+
+    auto fname = util::sprintf("irccd_%s_load", base);
+    auto load = dso_.get<load_t>(fname);
+
+    if (!load)
+        throw std::runtime_error(util::sprintf("missing plugin entry function '%s'", fname));
+
+    plugin_ = load(name, path);
+
+    if (!plugin_)
+        throw std::runtime_error("plugin returned null");
+}
+
+void dynlib_plugin::on_command(irccd& irccd, const message_event& ev)
+{
+    plugin_->on_command(irccd, ev);
+}
+
+void dynlib_plugin::on_connect(irccd& irccd, const connect_event& ev)
+{
+    plugin_->on_connect(irccd, ev);
+}
+
+void dynlib_plugin::on_channel_mode(irccd& irccd, const channel_mode_event& ev)
+{
+    plugin_->on_channel_mode(irccd, ev);
+}
+
+void dynlib_plugin::on_channel_notice(irccd& irccd, const channel_notice_event& ev)
+{
+    plugin_->on_channel_notice(irccd, ev);
+}
+
+void dynlib_plugin::on_invite(irccd& irccd, const invite_event& ev)
+{
+    plugin_->on_invite(irccd, ev);
+}
+
+void dynlib_plugin::on_join(irccd& irccd, const join_event& ev)
+{
+    plugin_->on_join(irccd, ev);
+}
+
+void dynlib_plugin::on_kick(irccd& irccd, const kick_event& ev)
+{
+    plugin_->on_kick(irccd, ev);
+}
+
+void dynlib_plugin::on_load(irccd& irccd)
+{
+    plugin_->on_load(irccd);
+}
+
+void dynlib_plugin::on_message(irccd& irccd, const message_event& ev)
+{
+    plugin_->on_message(irccd, ev);
+}
+
+void dynlib_plugin::on_me(irccd& irccd, const me_event& ev)
+{
+    plugin_->on_me(irccd, ev);
+}
+
+void dynlib_plugin::on_mode(irccd& irccd, const mode_event& ev)
+{
+    plugin_->on_mode(irccd, ev);
+}
+
+void dynlib_plugin::on_names(irccd& irccd, const names_event& ev)
+{
+    plugin_->on_names(irccd, ev);
+}
+
+void dynlib_plugin::on_nick(irccd& irccd, const nick_event& ev)
+{
+    plugin_->on_nick(irccd, ev);
+}
+
+void dynlib_plugin::on_notice(irccd& irccd, const notice_event& ev)
+{
+    plugin_->on_notice(irccd, ev);
+}
+
+void dynlib_plugin::on_part(irccd& irccd, const part_event& ev)
+{
+    plugin_->on_part(irccd, ev);
+}
+
+void dynlib_plugin::on_query(irccd& irccd, const query_event& ev)
+{
+    plugin_->on_query(irccd, ev);
+}
+
+void dynlib_plugin::on_query_command(irccd& irccd, const query_event& ev)
+{
+    plugin_->on_query_command(irccd, ev);
+}
+
+void dynlib_plugin::on_reload(irccd& irccd)
+{
+    plugin_->on_reload(irccd);
+}
+
+void dynlib_plugin::on_topic(irccd& irccd, const topic_event& ev)
+{
+    plugin_->on_topic(irccd, ev);
+}
+
+void dynlib_plugin::on_unload(irccd& irccd)
+{
+    plugin_->on_unload(irccd);
+}
+
+void dynlib_plugin::on_whois(irccd& irccd, const whois_event& ev)
+{
+    plugin_->on_whois(irccd, ev);
+}
+
+dynlib_plugin_loader::dynlib_plugin_loader(std::vector<std::string> directories) noexcept
+    : plugin_loader(std::move(directories), { DYNLIB_EXTENSION })
+{
+}
+
+std::shared_ptr<plugin> dynlib_plugin_loader::open(const std::string& id,
+                                                   const std::string& path) noexcept
+{
+    return std::make_unique<dynlib_plugin>(id, path);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/dynlib_plugin.hpp	Mon Oct 02 13:05:30 2017 +0200
@@ -0,0 +1,179 @@
+/*
+ * dynlib_plugin.hpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_DYNLIB_HPP
+#define IRCCD_PLUGIN_DYNLIB_HPP
+
+/**
+ * \file plugin-dynlib.hpp
+ * \brief Native plugin implementation.
+ */
+
+#include <boost/dll.hpp>
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Dynlib based plugin.
+ * \ingroup plugins
+ */
+class dynlib_plugin : public plugin {
+private:
+    boost::dll::shared_library dso_;
+    std::unique_ptr<plugin> plugin_;
+
+public:
+    /**
+     * Construct the plugin.
+     *
+     * \param name the name
+     * \param path the fully resolved path (must be absolute)
+     * \throw std::exception on failures
+     */
+    dynlib_plugin(std::string name, std::string path);
+
+    /**
+     * \copydoc plugin::on_command
+     */
+    void on_command(irccd& irccd, const message_event& event) override;
+
+    /**
+     * \copydoc plugin::on_connect
+     */
+    void on_connect(irccd& irccd, const connect_event& event) override;
+
+    /**
+     * \copydoc plugin::on_channel_mode
+     */
+    void on_channel_mode(irccd& irccd, const channel_mode_event& event) override;
+
+    /**
+     * \copydoc plugin::on_channel_notice
+     */
+    void on_channel_notice(irccd& irccd, const channel_notice_event& event) override;
+
+    /**
+     * \copydoc plugin::on_invite
+     */
+    void on_invite(irccd& irccd, const invite_event& event) override;
+
+    /**
+     * \copydoc plugin::on_join
+     */
+    void on_join(irccd& irccd, const join_event& event) override;
+
+    /**
+     * \copydoc plugin::on_kick
+     */
+    void on_kick(irccd& irccd, const kick_event& event) override;
+
+    /**
+     * \copydoc plugin::on_load
+     */
+    void on_load(irccd& irccd) override;
+
+    /**
+     * \copydoc plugin::on_message
+     */
+    void on_message(irccd& irccd, const message_event& event) override;
+
+    /**
+     * \copydoc plugin::on_me
+     */
+    void on_me(irccd& irccd, const me_event& event) override;
+
+    /**
+     * \copydoc plugin::on_mode
+     */
+    void on_mode(irccd& irccd, const mode_event& event) override;
+
+    /**
+     * \copydoc plugin::on_names
+     */
+    void on_names(irccd& irccd, const names_event& event) override;
+
+    /**
+     * \copydoc plugin::on_nick
+     */
+    void on_nick(irccd& irccd, const nick_event& event) override;
+
+    /**
+     * \copydoc plugin::on_notice
+     */
+    void on_notice(irccd& irccd, const notice_event& event) override;
+
+    /**
+     * \copydoc plugin::on_part
+     */
+    void on_part(irccd& irccd, const part_event& event) override;
+
+    /**
+     * \copydoc plugin::on_query
+     */
+    void on_query(irccd& irccd, const query_event& event) override;
+
+    /**
+     * \copydoc plugin::on_query_command
+     */
+    void on_query_command(irccd& irccd, const query_event& event) override;
+
+    /**
+     * \copydoc plugin::on_reload
+     */
+    void on_reload(irccd& irccd) override;
+
+    /**
+     * \copydoc plugin::on_topic
+     */
+    void on_topic(irccd& irccd, const topic_event& event) override;
+
+    /**
+     * \copydoc plugin::on_unload
+     */
+    void on_unload(irccd& irccd) override;
+
+    /**
+     * \copydoc plugin::on_whois
+     */
+    void on_whois(irccd& irccd, const whois_event& event) override;
+};
+
+/**
+ * \brief Implementation for searching native plugins.
+ */
+class dynlib_plugin_loader : public plugin_loader {
+public:
+    /**
+     * Constructor.
+     *
+     * \param directories optional directories to search, if empty use defaults.
+     */
+    dynlib_plugin_loader(std::vector<std::string> directories = {}) noexcept;
+
+    /**
+     * \copydoc plugin_loader::find
+     */
+    std::shared_ptr<plugin> open(const std::string& id,
+                                 const std::string& path) noexcept override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- a/libirccd/irccd/plugin-dynlib.cpp	Mon Oct 02 13:04:17 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-/*
- * plugin-dynlib.cpp -- native plugin implementation
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "fs.hpp"
-#include "logger.hpp"
-#include "plugin-dynlib.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename Sym>
-inline Sym sym(Dynlib &dynlib, const std::string &name)
-{
-    try {
-        return dynlib.sym<Sym>(name);
-    } catch (...) {
-        return nullptr;
-    }
-}
-
-template <typename Sym, typename... Args>
-inline void call(Sym sym, Args&&... args)
-{
-    if (sym)
-        sym(std::forward<Args>(args)...);
-}
-
-} // !namespace
-
-DynlibPlugin::DynlibPlugin(std::string name, std::string path)
-    : Plugin(name, path)
-    , m_dso(std::move(path))
-    , m_onCommand(sym<OnCommand>(m_dso, "irccd_onCommand"))
-    , m_onConnect(sym<OnConnect>(m_dso, "irccd_onConnect"))
-    , m_onChannelMode(sym<OnChannelMode>(m_dso, "irccd_onChannelMode"))
-    , m_onChannelNotice(sym<OnChannelNotice>(m_dso, "irccd_onChannelNotice"))
-    , m_onInvite(sym<OnInvite>(m_dso, "irccd_onInvite"))
-    , m_onJoin(sym<OnJoin>(m_dso, "irccd_onJoin"))
-    , m_onKick(sym<OnKick>(m_dso, "irccd_onKick"))
-    , m_onLoad(sym<OnLoad>(m_dso, "irccd_onLoad"))
-    , m_onMessage(sym<OnMessage>(m_dso, "irccd_onMessage"))
-    , m_onMe(sym<OnMe>(m_dso, "irccd_onMe"))
-    , m_onMode(sym<OnMode>(m_dso, "irccd_onMode"))
-    , m_onNames(sym<OnNames>(m_dso, "irccd_onNames"))
-    , m_onNick(sym<OnNick>(m_dso, "irccd_onNick"))
-    , m_onNotice(sym<OnNotice>(m_dso, "irccd_onNotice"))
-    , m_onPart(sym<OnPart>(m_dso, "irccd_onPart"))
-    , m_onQuery(sym<OnQuery>(m_dso, "irccd_onQuery"))
-    , m_onQueryCommand(sym<OnQueryCommand>(m_dso, "irccd_onQueryCommand"))
-    , m_onReload(sym<OnReload>(m_dso, "irccd_onReload"))
-    , m_onTopic(sym<OnTopic>(m_dso, "irccd_onTopic"))
-    , m_onUnload(sym<OnUnload>(m_dso, "irccd_onUnload"))
-    , m_onWhois(sym<OnWhois>(m_dso, "irccd_onWhois"))
-{
-}
-
-void DynlibPlugin::onCommand(Irccd &irccd, const MessageEvent &ev)
-{
-    call(m_onCommand, irccd, ev);
-}
-
-void DynlibPlugin::onConnect(Irccd &irccd, const ConnectEvent &ev)
-{
-    call(m_onConnect, irccd, ev);
-}
-
-void DynlibPlugin::onChannelMode(Irccd &irccd, const ChannelModeEvent &ev)
-{
-    call(m_onChannelMode, irccd, ev);
-}
-
-void DynlibPlugin::onChannelNotice(Irccd &irccd, const ChannelNoticeEvent &ev)
-{
-    call(m_onChannelNotice, irccd, ev);
-}
-
-void DynlibPlugin::onInvite(Irccd &irccd, const InviteEvent &ev)
-{
-    call(m_onInvite, irccd, ev);
-}
-
-void DynlibPlugin::onJoin(Irccd &irccd, const JoinEvent &ev)
-{
-    call(m_onJoin, irccd, ev);
-}
-
-void DynlibPlugin::onKick(Irccd &irccd, const KickEvent &ev)
-{
-    call(m_onKick, irccd, ev);
-}
-
-void DynlibPlugin::onLoad(Irccd &irccd)
-{
-    call(m_onLoad, irccd, *this);
-}
-
-void DynlibPlugin::onMessage(Irccd &irccd, const MessageEvent &ev)
-{
-    call(m_onMessage, irccd, ev);
-}
-
-void DynlibPlugin::onMe(Irccd &irccd, const MeEvent &ev)
-{
-    call(m_onMe, irccd, ev);
-}
-
-void DynlibPlugin::onMode(Irccd &irccd, const ModeEvent &ev)
-{
-    call(m_onMode, irccd, ev);
-}
-
-void DynlibPlugin::onNames(Irccd &irccd, const NamesEvent &ev)
-{
-    call(m_onNames, irccd, ev);
-}
-
-void DynlibPlugin::onNick(Irccd &irccd, const NickEvent &ev)
-{
-    call(m_onNick, irccd, ev);
-}
-
-void DynlibPlugin::onNotice(Irccd &irccd, const NoticeEvent &ev)
-{
-    call(m_onNotice, irccd, ev);
-}
-
-void DynlibPlugin::onPart(Irccd &irccd, const PartEvent &ev)
-{
-    call(m_onPart, irccd, ev);
-}
-
-void DynlibPlugin::onQuery(Irccd &irccd, const QueryEvent &ev)
-{
-    call(m_onQuery, irccd, ev);
-}
-
-void DynlibPlugin::onQueryCommand(Irccd &irccd, const QueryEvent &ev)
-{
-    call(m_onQueryCommand, irccd, ev);
-}
-
-void DynlibPlugin::onReload(Irccd &irccd)
-{
-    call(m_onReload, irccd, *this);
-}
-
-void DynlibPlugin::onTopic(Irccd &irccd, const TopicEvent &ev)
-{
-    call(m_onTopic, irccd, ev);
-}
-
-void DynlibPlugin::onUnload(Irccd &irccd)
-{
-    call(m_onUnload, irccd, *this);
-}
-
-void DynlibPlugin::onWhois(Irccd &irccd, const WhoisEvent &ev)
-{
-    call(m_onWhois, irccd, ev);
-}
-
-std::shared_ptr<Plugin> DynlibPluginLoader::open(const std::string &,
-                                                 const std::string &) noexcept
-{
-    // TODO: dynlib plugins are unsupported for now.
-    return nullptr;
-}
-
-std::shared_ptr<Plugin> DynlibPluginLoader::find(const std::string &) noexcept
-{
-    // TODO: dynlib plugins are unsupported for now.
-    return nullptr;
-}
-
-} // !irccd
--- a/libirccd/irccd/plugin-dynlib.hpp	Mon Oct 02 13:04:17 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- * plugin-dynlib.hpp -- native plugin implementation
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_PLUGIN_DYNLIB_HPP
-#define IRCCD_PLUGIN_DYNLIB_HPP
-
-/**
- * \file plugin-dynlib.hpp
- * \brief Native plugin implementation.
- */
-
-#include "dynlib.hpp"
-#include "plugin.hpp"
-
-namespace irccd {
-
-/**
- * \brief Dynlib based plugin.
- * \ingroup plugins
- */
-class DynlibPlugin : public Plugin {
-private:
-    using OnCommand = void (*)(Irccd &, const MessageEvent &);
-    using OnConnect = void (*)(Irccd &, const ConnectEvent &);
-    using OnChannelMode = void (*)(Irccd &, const ChannelModeEvent &);
-    using OnChannelNotice = void (*)(Irccd &, const ChannelNoticeEvent &);
-    using OnInvite = void (*)(Irccd &, const InviteEvent &);
-    using OnJoin = void (*)(Irccd &, const JoinEvent &);
-    using OnKick = void (*)(Irccd &, const KickEvent &);
-    using OnLoad = void (*)(Irccd &, DynlibPlugin &);
-    using OnMessage = void (*)(Irccd &, const MessageEvent &);
-    using OnMe = void (*)(Irccd &, const MeEvent &);
-    using OnMode = void (*)(Irccd &, const ModeEvent &);
-    using OnNames = void (*)(Irccd &, const NamesEvent &);
-    using OnNick = void (*)(Irccd &, const NickEvent &);
-    using OnNotice = void (*)(Irccd &, const NoticeEvent &);
-    using OnPart = void (*)(Irccd &, const PartEvent &);
-    using OnQuery = void (*)(Irccd &, const QueryEvent &);
-    using OnQueryCommand = void (*)(Irccd &, const QueryEvent &);
-    using OnReload = void (*)(Irccd &, DynlibPlugin &);
-    using OnTopic = void (*)(Irccd &, const TopicEvent &);
-    using OnUnload = void (*)(Irccd &, DynlibPlugin &);
-    using OnWhois = void (*)(Irccd &, const WhoisEvent &);
-
-    Dynlib m_dso;
-    OnCommand m_onCommand;
-    OnConnect m_onConnect;
-    OnChannelMode m_onChannelMode;
-    OnChannelNotice m_onChannelNotice;
-    OnInvite m_onInvite;
-    OnJoin m_onJoin;
-    OnKick m_onKick;
-    OnLoad m_onLoad;
-    OnMessage m_onMessage;
-    OnMe m_onMe;
-    OnMode m_onMode;
-    OnNames m_onNames;
-    OnNick m_onNick;
-    OnNotice m_onNotice;
-    OnPart m_onPart;
-    OnQuery m_onQuery;
-    OnQueryCommand m_onQueryCommand;
-    OnReload m_onReload;
-    OnTopic m_onTopic;
-    OnUnload m_onUnload;
-    OnWhois m_onWhois;
-
-    // Configuration and formats.
-    plugin_config m_config;
-    plugin_formats m_formats;
-
-public:
-    /**
-     * Construct the plugin.
-     *
-     * \param name the name
-     * \param path the fully resolved path (must be absolute)
-     * \throw std::exception on failures
-     */
-    DynlibPlugin(std::string name, std::string path);
-
-    /**
-     * \copydoc Plugin::onCommand
-     */
-    IRCCD_EXPORT void onCommand(irccd &irccd, const MessageEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onConnect
-     */
-    IRCCD_EXPORT void onConnect(irccd &irccd, const ConnectEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onChannelMode
-     */
-    IRCCD_EXPORT void onChannelMode(irccd &irccd, const ChannelModeEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onChannelNotice
-     */
-    IRCCD_EXPORT void onChannelNotice(irccd &irccd, const ChannelNoticeEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onInvite
-     */
-    IRCCD_EXPORT void onInvite(irccd &irccd, const InviteEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onJoin
-     */
-    IRCCD_EXPORT void onJoin(irccd &irccd, const JoinEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onKick
-     */
-    IRCCD_EXPORT void onKick(irccd &irccd, const KickEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onLoad
-     */
-    IRCCD_EXPORT void onLoad(irccd &irccd) override;
-
-    /**
-     * \copydoc Plugin::onMessage
-     */
-    IRCCD_EXPORT void onMessage(irccd &irccd, const MessageEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onMe
-     */
-    IRCCD_EXPORT void onMe(irccd &irccd, const MeEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onMode
-     */
-    IRCCD_EXPORT void onMode(irccd &irccd, const ModeEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onNames
-     */
-    IRCCD_EXPORT void onNames(irccd &irccd, const NamesEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onNick
-     */
-    IRCCD_EXPORT void onNick(irccd &irccd, const NickEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onNotice
-     */
-    IRCCD_EXPORT void onNotice(irccd &irccd, const NoticeEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onPart
-     */
-    IRCCD_EXPORT void onPart(irccd &irccd, const PartEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onQuery
-     */
-    IRCCD_EXPORT void onQuery(irccd &irccd, const QueryEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onQueryCommand
-     */
-    IRCCD_EXPORT void onQueryCommand(irccd &irccd, const QueryEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onReload
-     */
-    IRCCD_EXPORT void onReload(irccd &irccd) override;
-
-    /**
-     * \copydoc Plugin::onTopic
-     */
-    IRCCD_EXPORT void onTopic(irccd &irccd, const TopicEvent &event) override;
-
-    /**
-     * \copydoc Plugin::onUnload
-     */
-    IRCCD_EXPORT void onUnload(irccd &irccd) override;
-
-    /**
-     * \copydoc Plugin::onWhois
-     */
-    IRCCD_EXPORT void onWhois(irccd &irccd, const WhoisEvent &event) override;
-};
-
-/**
- * \brief Implementation for searching native plugins.
- */
-class DynlibPluginLoader : public PluginLoader {
-public:
-    /**
-     * \copydoc PluginLoader::find
-     */
-    std::shared_ptr<Plugin> open(const std::string &id,
-                                 const std::string &path) noexcept override;
-
-    /**
-     * \copydoc PluginLoader::find
-     */
-    std::shared_ptr<Plugin> find(const std::string &id) noexcept override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- a/tests/CMakeLists.txt	Mon Oct 02 13:04:17 2017 +0200
+++ b/tests/CMakeLists.txt	Mon Oct 02 13:05:30 2017 +0200
@@ -49,6 +49,7 @@
     add_subdirectory(cmd-server-part)
     add_subdirectory(cmd-server-reconnect)
     add_subdirectory(cmd-server-topic)
+    add_subdirectory(dynlib_plugin)
 
     # Misc
     add_subdirectory(logger)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dynlib_plugin/CMakeLists.txt	Mon Oct 02 13:05:30 2017 +0200
@@ -0,0 +1,43 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+find_package(Boost REQUIRED)
+
+add_library(test-plugin MODULE test_plugin.cpp)
+target_link_libraries(test-plugin libirccd Boost::boost)
+set_target_properties(
+    test-plugin
+    PROPERTIES
+        PREFIX ""
+        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+foreach (c ${CMAKE_CONFIGURATION_TYPES})
+    string(TOUPPER ${c} c)
+    set_target_properties(
+        test-plugin
+        PROPERTIES
+            RUNTIME_OUTPUT_DIRECTORY_${c} ${CMAKE_CURRENT_BINARY_DIR}
+    )
+endforeach ()
+
+irccd_define_test(
+    NAME dynlib-plugin
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccd-test
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dynlib_plugin/main.cpp	Mon Oct 02 13:05:30 2017 +0200
@@ -0,0 +1,237 @@
+/*
+ * main.cpp -- test dynlib_plugin
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "dynlib_plugin"
+#include <boost/test/unit_test.hpp>
+
+/*
+ * For this test, we open a plugin written in C++ and pass a journal_server
+ * class for each of the plugin function.
+ *
+ * Then we verify that the appropriate function has been called correctly.
+ *
+ * Functions load, unload and reload can not be tested though.
+ */
+
+#include <irccd.hpp>
+#include <dynlib_plugin.hpp>
+#include <journal_server.hpp>
+
+namespace irccd {
+
+class fixture {
+protected:
+    std::shared_ptr<journal_server> server_;
+    std::shared_ptr<plugin> plugin_;
+    irccd irccd_;
+
+    inline fixture()
+        : server_(std::make_shared<journal_server>("test"))
+    {
+        plugin_ = dynlib_plugin_loader({CMAKE_CURRENT_BINARY_DIR}).find("test-plugin");
+
+        if (!plugin_)
+            throw std::runtime_error("test plugin not found");
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(dynlib_plugin_suite, fixture)
+
+BOOST_AUTO_TEST_CASE(on_command)
+{
+    plugin_->on_command(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_command");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_connect)
+{
+    plugin_->on_connect(irccd_, {server_});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_connect");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_channel_mode)
+{
+    plugin_->on_channel_mode(irccd_, {server_, "", "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_channel_mode");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_channel_notice)
+{
+    plugin_->on_channel_notice(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_channel_notice");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_invite)
+{
+    plugin_->on_invite(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_invite");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_join)
+{
+    plugin_->on_join(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_join");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_kick)
+{
+    plugin_->on_kick(irccd_, {server_, "", "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_kick");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_message)
+{
+    plugin_->on_message(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_message");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_me)
+{
+    plugin_->on_me(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_me");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_mode)
+{
+    plugin_->on_mode(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_mode");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_names)
+{
+    plugin_->on_names(irccd_, {server_, "", {}});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_names");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_nick)
+{
+    plugin_->on_nick(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_nick");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_notice)
+{
+    plugin_->on_notice(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_notice");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_part)
+{
+    plugin_->on_part(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_part");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_query)
+{
+    plugin_->on_query(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_query");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_query_command)
+{
+    plugin_->on_query_command(irccd_, {server_, "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_query_command");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_query_topic)
+{
+    plugin_->on_topic(irccd_, {server_, "", "", ""});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_topic");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_CASE(on_whois)
+{
+    plugin_->on_whois(irccd_, {server_, {"", "", "", "", {}}});
+
+    BOOST_TEST(server_->cqueue().size() == 1U);
+    BOOST_TEST(server_->cqueue()[0]["command"].get<std::string>() == "message");
+    BOOST_TEST(server_->cqueue()[0]["message"].get<std::string>() == "on_whois");
+    BOOST_TEST(server_->cqueue()[0]["target"].get<std::string>() == "test");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dynlib_plugin/test_plugin.cpp	Mon Oct 02 13:05:30 2017 +0200
@@ -0,0 +1,130 @@
+/*
+ * test_plugin.cpp -- basic exported plugin test
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <boost/dll.hpp>
+
+#include <plugin.hpp>
+
+namespace irccd {
+
+class test_plugin : public plugin {
+public:
+    using plugin::plugin;
+
+    void on_command(irccd&, const message_event& event) override
+    {
+        event.server->message("test", "on_command");
+    }
+
+    void on_connect(irccd&, const connect_event& event) override
+    {
+        event.server->message("test", "on_connect");
+    }
+
+    void on_channel_mode(irccd&, const channel_mode_event& event) override
+    {
+        event.server->message("test", "on_channel_mode");
+    }
+
+    void on_channel_notice(irccd&, const channel_notice_event& event) override
+    {
+        event.server->message("test", "on_channel_notice");
+    }
+
+    void on_invite(irccd&, const invite_event& event) override
+    {
+        event.server->message("test", "on_invite");
+    }
+
+    void on_join(irccd&, const join_event& event) override
+    {
+        event.server->message("test", "on_join");
+    }
+
+    void on_kick(irccd&, const kick_event& event) override
+    {
+        event.server->message("test", "on_kick");
+    }
+
+    void on_message(irccd&, const message_event& event) override
+    {
+        event.server->message("test", "on_message");
+    }
+
+    void on_me(irccd&, const me_event& event) override
+    {
+        event.server->message("test", "on_me");
+    }
+
+    void on_mode(irccd&, const mode_event& event) override
+    {
+        event.server->message("test", "on_mode");
+    }
+
+    void on_names(irccd&, const names_event& event) override
+    {
+        event.server->message("test", "on_names");
+    }
+
+    void on_nick(irccd&, const nick_event& event) override
+    {
+        event.server->message("test", "on_nick");
+    }
+
+    void on_notice(irccd&, const notice_event& event) override
+    {
+        event.server->message("test", "on_notice");
+    }
+
+    void on_part(irccd&, const part_event& event) override
+    {
+        event.server->message("test", "on_part");
+    }
+
+    void on_query(irccd&, const query_event& event) override
+    {
+        event.server->message("test", "on_query");
+    }
+
+    void on_query_command(irccd&, const query_event& event) override
+    {
+        event.server->message("test", "on_query_command");
+    }
+
+    void on_topic(irccd&, const topic_event& event) override
+    {
+        event.server->message("test", "on_topic");
+    }
+
+    void on_whois(irccd&, const whois_event& event) override
+    {
+        event.server->message("test", "on_whois");
+    }
+};
+
+} // !irccd
+
+extern "C" {
+
+BOOST_SYMBOL_EXPORT
+std::unique_ptr<irccd::plugin> irccd_testplugin_load(std::string name, std::string path)
+{
+    return std::make_unique<irccd::test_plugin>(name, path);
+}
+
+} // !C