changeset 669:6eb4caea77a5

Tests: split libirccd util tests into libcommon, closes #789 @1h While here, remove unneeded and deprecated functions from string_util and adapt existing code to new functions.
author David Demelier <markand@malikania.fr>
date Fri, 06 Apr 2018 22:06:07 +0200
parents 8a79b5c0ddc7
children 95ac3ace1610
files CMakeLists.txt cmake/check/GidIsSigned.cmake cmake/check/UidIsSigned.cmake cmake/internal/sysconfig.hpp.in libcommon/irccd/string_util.cpp libcommon/irccd/string_util.hpp libcommon/irccd/system.cpp tests/src/libcommon/CMakeLists.txt tests/src/libcommon/fs-util/CMakeLists.txt tests/src/libcommon/fs-util/main.cpp tests/src/libcommon/string-util/CMakeLists.txt tests/src/libcommon/string-util/main.cpp tests/src/libirccd/CMakeLists.txt tests/src/libirccd/util/CMakeLists.txt tests/src/libirccd/util/main.cpp
diffstat 15 files changed, 592 insertions(+), 559 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Apr 06 21:10:34 2018 +0200
+++ b/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
@@ -70,6 +70,8 @@
 include(cmake/function/IrccdIndentMessage.cmake)
 
 include(cmake/check/PutTime.cmake)
+include(cmake/check/UidIsSigned.cmake)
+include(cmake/check/GidIsSigned.cmake)
 
 include(cmake/IrccdVersion.cmake)
 include(cmake/IrccdOptions.cmake)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/check/GidIsSigned.cmake	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,33 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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(CheckCXXSourceCompiles)
+
+# Check if gid_t is signed.
+check_cxx_source_compiles(
+    "#include <sys/types.h>
+
+     #include <type_traits>
+
+     int main()
+     {
+        static_assert(std::is_signed<gid_t>::value, \"gid is signed\");
+     }
+    "
+    HAVE_SIGNED_GID_T
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/check/UidIsSigned.cmake	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,33 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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(CheckCXXSourceCompiles)
+
+# Check if uid_t is signed.
+check_cxx_source_compiles(
+    "#include <sys/types.h>
+
+     #include <type_traits>
+
+     int main()
+     {
+        static_assert(std::is_signed<uid_t>::value, \"uid is signed\");
+     }
+    "
+    HAVE_SIGNED_UID_T
+)
--- a/cmake/internal/sysconfig.hpp.in	Fri Apr 06 21:10:34 2018 +0200
+++ b/cmake/internal/sysconfig.hpp.in	Fri Apr 06 22:06:07 2018 +0200
@@ -87,7 +87,8 @@
 #cmakedefine HAVE_SETGID
 #cmakedefine HAVE_SETPROGNAME
 #cmakedefine HAVE_SETUID
-#cmakedefine HAVE_STD_PUT_TIME
+#cmakedefine HAVE_SIGNED_GID_T
+#cmakedefine HAVE_SIGNED_UID_T
 #cmakedefine HAVE_STAT
 #cmakedefine HAVE_STAT_ST_ATIME
 #cmakedefine HAVE_STAT_ST_BLKSIZE
@@ -102,6 +103,7 @@
 #cmakedefine HAVE_STAT_ST_RDEV
 #cmakedefine HAVE_STAT_ST_SIZE
 #cmakedefine HAVE_STAT_ST_UID
+#cmakedefine HAVE_STD_PUT_TIME
 #cmakedefine HAVE_SYSLOG
 
 /*
--- a/libcommon/irccd/string_util.cpp	Fri Apr 06 21:10:34 2018 +0200
+++ b/libcommon/irccd/string_util.cpp	Fri Apr 06 22:06:07 2018 +0200
@@ -344,30 +344,6 @@
     return value == "1" || value == "YES" || value == "TRUE" || value == "ON";
 }
 
-bool is_int(const std::string &str, int base) noexcept
-{
-    if (str.empty())
-        return false;
-
-    char *ptr;
-
-    std::strtol(str.c_str(), &ptr, base);
-
-    return *ptr == 0;
-}
-
-bool is_real(const std::string &str) noexcept
-{
-    if (str.empty())
-        return false;
-
-    char *ptr;
-
-    std::strtod(str.c_str(), &ptr);
-
-    return *ptr == 0;
-}
-
 } // !string_util
 
 } // !util
--- a/libcommon/irccd/string_util.hpp	Fri Apr 06 21:10:34 2018 +0200
+++ b/libcommon/irccd/string_util.hpp	Fri Apr 06 22:06:07 2018 +0200
@@ -275,34 +275,6 @@
 IRCCD_EXPORT bool is_boolean(std::string value) noexcept;
 
 /**
- * Check if the string is an integer.
- *
- * \param value the input
- * \param base the optional base
- * \return true if integer
- */
-IRCCD_EXPORT bool is_int(const std::string& value, int base = 10) noexcept;
-
-/**
- * Check if the string is real.
- *
- * \param value the value
- * \return true if real
- */
-IRCCD_EXPORT bool is_real(const std::string& value) noexcept;
-
-/**
- * Check if the string is a number.
- *
- * \param value the value
- * \return true if it is a number
- */
-inline bool is_number(const std::string& value) noexcept
-{
-    return is_int(value) || is_real(value);
-}
-
-/**
  * \cond HIDDEN_SYMBOLS
  */
 
--- a/libcommon/irccd/system.cpp	Fri Apr 06 21:10:34 2018 +0200
+++ b/libcommon/irccd/system.cpp	Fri Apr 06 22:06:07 2018 +0200
@@ -478,11 +478,13 @@
 {
     assert(value.c_str());
 
-    uid_t id;
+#if defined(HAVE_SIGNED_UID_T)
+    auto id = string_util::to_int<uid_t>(value);
+#else
+    auto id = string_util::to_uint<uid_t>(value);
+#endif
 
-    if (string_util::is_int(value))
-        id = std::stoi(value);
-    else {
+    if (!id) {
         auto pw = getpwnam(value.c_str());
 
         if (!pw)
@@ -491,7 +493,7 @@
         id = pw->pw_uid;
     }
 
-    if (setuid(id) < 0)
+    if (setuid(*id) < 0)
         throw std::runtime_error(std::strerror(errno));
 }
 
@@ -503,11 +505,13 @@
 {
     assert(value.c_str());
 
-    gid_t id;
+#if defined(HAVE_SIGNED_GID_T)
+    auto id = string_util::to_int<gid_t>(value);
+#else
+    auto id = string_util::to_uint<gid_t>(value);
+#endif
 
-    if (string_util::is_int(value))
-        id = std::stoi(value);
-    else {
+    if (!id) {
         auto gr = getgrnam(value.c_str());
 
         if (!gr)
@@ -516,7 +520,7 @@
         id = gr->gr_gid;
     }
 
-    if (setgid(id) < 0)
+    if (setgid(*id) < 0)
         throw std::runtime_error(std::strerror(errno));
 }
 
--- a/tests/src/libcommon/CMakeLists.txt	Fri Apr 06 21:10:34 2018 +0200
+++ b/tests/src/libcommon/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
@@ -16,4 +16,6 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
+add_subdirectory(fs-util)
 add_subdirectory(network-stream)
+add_subdirectory(string-util)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/fs-util/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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.
+#
+
+irccd_define_test(
+    NAME fs-util
+    SOURCES main.cpp
+    LIBRARIES libcommon
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/fs-util/main.cpp	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,79 @@
+/*
+ * main.cpp -- test fs_util functions
+ *
+ * Copyright (c) 2013-2018 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 "fs_util"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/fs_util.hpp>
+#include <irccd/system.hpp>
+
+namespace irccd {
+
+/*
+ * fs_util::find function (name)
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(fs_find_name)
+
+BOOST_AUTO_TEST_CASE(not_recursive)
+{
+    auto file1 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-1.txt", false);
+    auto file2 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-2.txt", false);
+
+    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
+    BOOST_TEST(file2.empty());
+}
+
+BOOST_AUTO_TEST_CASE(recursive)
+{
+    auto file1 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-1.txt", true);
+    auto file2 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-2.txt", true);
+
+    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
+    BOOST_TEST(file2.find("file-2.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * fs_util::find function (regex)
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(fs_find_regex)
+
+BOOST_AUTO_TEST_CASE(not_recursive)
+{
+    const std::regex regex("file-[12]\\.txt");
+    const auto file = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", regex, false);
+
+    BOOST_TEST(file.find("file-1.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_CASE(recursive)
+{
+    const std::regex regex("file-[12]\\.txt");
+    const auto file = fs_util::find(CMAKE_SOURCE_DIR "/tests/root/level-1", regex, true);
+
+    BOOST_TEST(file.find("file-2.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/string-util/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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.
+#
+
+irccd_define_test(
+    NAME string-util
+    SOURCES main.cpp
+    LIBRARIES libcommon
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libcommon/string-util/main.cpp	Fri Apr 06 22:06:07 2018 +0200
@@ -0,0 +1,380 @@
+/*
+ * main.cpp -- test string_util functions
+ *
+ * Copyright (c) 2013-2018 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 "string_util"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+
+namespace irccd {
+
+/*
+ * string_util::format function
+ * --------------------------------------------------------
+ */
+BOOST_AUTO_TEST_SUITE(format)
+
+BOOST_AUTO_TEST_CASE(nothing)
+{
+    std::string expected = "hello world!";
+    std::string result = string_util::format("hello world!");
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(escape)
+{
+    string_util::subst params;
+
+    params.keywords.emplace("target", "hello");
+
+    BOOST_TEST(string_util::format("$@#") == "$@#");
+    BOOST_TEST(string_util::format(" $ @ # ") == " $ @ # ");
+    BOOST_TEST(string_util::format("#") == "#");
+    BOOST_TEST(string_util::format(" # ") == " # ");
+    BOOST_TEST(string_util::format("#@") == "#@");
+    BOOST_TEST(string_util::format("##") == "##");
+    BOOST_TEST(string_util::format("#!") == "#!");
+    BOOST_TEST(string_util::format("##{target}") == "#{target}");
+    BOOST_TEST(string_util::format("@#{target}", params) == "@hello");
+    BOOST_TEST(string_util::format("#{target}#", params) == "hello#");
+    BOOST_REQUIRE_THROW(string_util::format("#{failure"), std::exception);
+}
+
+BOOST_AUTO_TEST_CASE(disable_date)
+{
+    string_util::subst params;
+
+    params.flags &= ~(string_util::subst_flags::date);
+
+    BOOST_TEST(string_util::format("%H:%M", params) == "%H:%M");
+}
+
+BOOST_AUTO_TEST_CASE(disable_keywords)
+{
+    string_util::subst params;
+
+    params.keywords.emplace("target", "hello");
+    params.flags &= ~(string_util::subst_flags::keywords);
+
+    BOOST_TEST(string_util::format("#{target}", params) == "#{target}");
+}
+
+BOOST_AUTO_TEST_CASE(disable_env)
+{
+    string_util::subst params;
+
+    params.flags &= ~(string_util::subst_flags::env);
+
+    BOOST_TEST(string_util::format("${HOME}", params) == "${HOME}");
+}
+
+BOOST_AUTO_TEST_CASE(keyword_simple)
+{
+    string_util::subst params;
+
+    params.keywords.insert({"target", "irccd"});
+
+    std::string expected = "hello irccd!";
+    std::string result = string_util::format("hello #{target}!", params);
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(keyword_multiple)
+{
+    string_util::subst params;
+
+    params.keywords.insert({"target", "irccd"});
+    params.keywords.insert({"source", "nightmare"});
+
+    std::string expected = "hello irccd from nightmare!";
+    std::string result = string_util::format("hello #{target} from #{source}!", params);
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(keyword_adj_twice)
+{
+    string_util::subst params;
+
+    params.keywords.insert({"target", "irccd"});
+
+    std::string expected = "hello irccdirccd!";
+    std::string result = string_util::format("hello #{target}#{target}!", params);
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(keyword_missing)
+{
+    std::string expected = "hello !";
+    std::string result = string_util::format("hello #{target}!");
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(env_simple)
+{
+    std::string home = sys::env("HOME");
+
+    if (!home.empty()) {
+        std::string expected = "my home is " + home;
+        std::string result = string_util::format("my home is ${HOME}");
+
+        BOOST_TEST(expected == result);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(env_missing)
+{
+    std::string expected = "value is ";
+    std::string result = string_util::format("value is ${HOPE_THIS_VAR_NOT_EXIST}");
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * string_util::split function
+ * --------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(split)
+
+using list = std::vector<std::string>;
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+    list expected { "a", "b" };
+    list result = string_util::split("a;b", ";");
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(cut)
+{
+    list expected { "msg", "#staff", "foo bar baz" };
+    list result = string_util::split("msg;#staff;foo bar baz", ";", 3);
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * string_util::strip function
+ * --------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(strip)
+
+BOOST_AUTO_TEST_CASE(left)
+{
+    std::string value = "   123";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "123");
+}
+
+BOOST_AUTO_TEST_CASE(right)
+{
+    std::string value = "123   ";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "123");
+}
+
+BOOST_AUTO_TEST_CASE(both)
+{
+    std::string value = "   123   ";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "123");
+}
+
+BOOST_AUTO_TEST_CASE(none)
+{
+    std::string value = "without";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "without");
+}
+
+BOOST_AUTO_TEST_CASE(betweenEmpty)
+{
+    std::string value = "one list";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "one list");
+}
+
+BOOST_AUTO_TEST_CASE(betweenLeft)
+{
+    std::string value = "  space at left";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "space at left");
+}
+
+BOOST_AUTO_TEST_CASE(betweenRight)
+{
+    std::string value = "space at right  ";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "space at right");
+}
+
+BOOST_AUTO_TEST_CASE(betweenBoth)
+{
+    std::string value = "  space at both  ";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "space at both");
+}
+
+BOOST_AUTO_TEST_CASE(empty)
+{
+    std::string value = "    ";
+    std::string result = string_util::strip(value);
+
+    BOOST_TEST(result == "");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * string_util::join function
+ * --------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(join)
+
+BOOST_AUTO_TEST_CASE(empty)
+{
+    std::string expected = "";
+    std::string result = string_util::join<int>({});
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(one)
+{
+    std::string expected = "1";
+    std::string result = string_util::join({1});
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(two)
+{
+    std::string expected = "1:2";
+    std::string result = string_util::join({1, 2});
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(delimiterString)
+{
+    std::string expected = "1;;2;;3";
+    std::string result = string_util::join({1, 2, 3}, ";;");
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_CASE(delimiterChar)
+{
+    std::string expected = "1@2@3@4";
+    std::string result = string_util::join({1, 2, 3, 4}, '@');
+
+    BOOST_TEST(expected == result);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * string_util::is_identifier function
+ * --------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(is_identifier_valid)
+
+BOOST_AUTO_TEST_CASE(correct)
+{
+    BOOST_TEST(string_util::is_identifier("localhost"));
+    BOOST_TEST(string_util::is_identifier("localhost2"));
+    BOOST_TEST(string_util::is_identifier("localhost2-4_"));
+}
+
+BOOST_AUTO_TEST_CASE(incorrect)
+{
+    BOOST_TEST(!string_util::is_identifier(""));
+    BOOST_TEST(!string_util::is_identifier("localhost with spaces"));
+    BOOST_TEST(!string_util::is_identifier("localhost*"));
+    BOOST_TEST(!string_util::is_identifier("&&"));
+    BOOST_TEST(!string_util::is_identifier("@'"));
+    BOOST_TEST(!string_util::is_identifier("##"));
+    BOOST_TEST(!string_util::is_identifier("===++"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * string_util::is_boolean function
+ * --------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(is_boolean)
+
+BOOST_AUTO_TEST_CASE(correct)
+{
+    // true
+    BOOST_TEST(string_util::is_boolean("true"));
+    BOOST_TEST(string_util::is_boolean("True"));
+    BOOST_TEST(string_util::is_boolean("TRUE"));
+    BOOST_TEST(string_util::is_boolean("TruE"));
+
+    // yes
+    BOOST_TEST(string_util::is_boolean("yes"));
+    BOOST_TEST(string_util::is_boolean("Yes"));
+    BOOST_TEST(string_util::is_boolean("YES"));
+    BOOST_TEST(string_util::is_boolean("YeS"));
+
+    // on
+    BOOST_TEST(string_util::is_boolean("on"));
+    BOOST_TEST(string_util::is_boolean("On"));
+    BOOST_TEST(string_util::is_boolean("oN"));
+    BOOST_TEST(string_util::is_boolean("ON"));
+
+    // 1
+    BOOST_TEST(string_util::is_boolean("1"));
+}
+
+BOOST_AUTO_TEST_CASE(incorrect)
+{
+    BOOST_TEST(!string_util::is_boolean("false"));
+    BOOST_TEST(!string_util::is_boolean("lol"));
+    BOOST_TEST(!string_util::is_boolean(""));
+    BOOST_TEST(!string_util::is_boolean("0"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- a/tests/src/libirccd/CMakeLists.txt	Fri Apr 06 21:10:34 2018 +0200
+++ b/tests/src/libirccd/CMakeLists.txt	Fri Apr 06 22:06:07 2018 +0200
@@ -48,4 +48,3 @@
 add_subdirectory(irc)
 add_subdirectory(logger)
 add_subdirectory(rules)
-add_subdirectory(util)
--- a/tests/src/libirccd/util/CMakeLists.txt	Fri Apr 06 21:10:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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.
-#
-
-irccd_define_test(
-    NAME util
-    SOURCES main.cpp
-    LIBRARIES libirccd
-)
--- a/tests/src/libirccd/util/main.cpp	Fri Apr 06 21:10:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,472 +0,0 @@
-/*
- * main.cpp -- test util functions
- *
- * Copyright (c) 2013-2018 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 "util"
-#include <boost/test/unit_test.hpp>
-
-#include <cstdint>
-
-#include <irccd/fs_util.hpp>
-#include <irccd/string_util.hpp>
-#include <irccd/system.hpp>
-
-namespace std {
-
-std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& list)
-{
-    for (const auto& s : list)
-        out << s << " ";
-
-    return out;
-}
-
-} // !std
-
-namespace irccd {
-
-BOOST_AUTO_TEST_SUITE(format)
-
-/*
- * string_util::format function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(nothing)
-{
-    std::string expected = "hello world!";
-    std::string result = string_util::format("hello world!");
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(escape)
-{
-    string_util::subst params;
-
-    params.keywords.emplace("target", "hello");
-
-    BOOST_REQUIRE_EQUAL("$@#", string_util::format("$@#"));
-    BOOST_REQUIRE_EQUAL(" $ @ # ", string_util::format(" $ @ # "));
-    BOOST_REQUIRE_EQUAL("#", string_util::format("#"));
-    BOOST_REQUIRE_EQUAL(" # ", string_util::format(" # "));
-    BOOST_REQUIRE_EQUAL("#@", string_util::format("#@"));
-    BOOST_REQUIRE_EQUAL("##", string_util::format("##"));
-    BOOST_REQUIRE_EQUAL("#!", string_util::format("#!"));
-    BOOST_REQUIRE_EQUAL("#{target}", string_util::format("##{target}"));
-    BOOST_REQUIRE_EQUAL("@hello", string_util::format("@#{target}", params));
-    BOOST_REQUIRE_EQUAL("hello#", string_util::format("#{target}#", params));
-    BOOST_REQUIRE_THROW(string_util::format("#{failure"), std::exception);
-}
-
-BOOST_AUTO_TEST_CASE(disable_date)
-{
-    string_util::subst params;
-
-    params.flags &= ~(string_util::subst_flags::date);
-
-    BOOST_REQUIRE_EQUAL("%H:%M", string_util::format("%H:%M", params));
-}
-
-BOOST_AUTO_TEST_CASE(disable_keywords)
-{
-    string_util::subst params;
-
-    params.keywords.emplace("target", "hello");
-    params.flags &= ~(string_util::subst_flags::keywords);
-
-    BOOST_REQUIRE_EQUAL("#{target}", string_util::format("#{target}", params));
-}
-
-BOOST_AUTO_TEST_CASE(disable_env)
-{
-    string_util::subst params;
-
-    params.flags &= ~(string_util::subst_flags::env);
-
-    BOOST_REQUIRE_EQUAL("${HOME}", string_util::format("${HOME}", params));
-}
-
-BOOST_AUTO_TEST_CASE(keyword_simple)
-{
-    string_util::subst params;
-
-    params.keywords.insert({"target", "irccd"});
-
-    std::string expected = "hello irccd!";
-    std::string result = string_util::format("hello #{target}!", params);
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(keyword_multiple)
-{
-    string_util::subst params;
-
-    params.keywords.insert({"target", "irccd"});
-    params.keywords.insert({"source", "nightmare"});
-
-    std::string expected = "hello irccd from nightmare!";
-    std::string result = string_util::format("hello #{target} from #{source}!", params);
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(keyword_adj_twice)
-{
-    string_util::subst params;
-
-    params.keywords.insert({"target", "irccd"});
-
-    std::string expected = "hello irccdirccd!";
-    std::string result = string_util::format("hello #{target}#{target}!", params);
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(keyword_missing)
-{
-    std::string expected = "hello !";
-    std::string result = string_util::format("hello #{target}!");
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(env_simple)
-{
-    std::string home = sys::env("HOME");
-
-    if (!home.empty()) {
-        std::string expected = "my home is " + home;
-        std::string result = string_util::format("my home is ${HOME}");
-
-        BOOST_REQUIRE_EQUAL(expected, result);
-    }
-}
-
-BOOST_AUTO_TEST_CASE(env_missing)
-{
-    std::string expected = "value is ";
-    std::string result = string_util::format("value is ${HOPE_THIS_VAR_NOT_EXIST}");
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::split function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(split)
-
-using list = std::vector<std::string>;
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-    list expected { "a", "b" };
-    list result = string_util::split("a;b", ";");
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(cut)
-{
-    list expected { "msg", "#staff", "foo bar baz" };
-    list result = string_util::split("msg;#staff;foo bar baz", ";", 3);
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::strip function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(strip)
-
-BOOST_AUTO_TEST_CASE(left)
-{
-    std::string value = "   123";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("123", result);
-}
-
-BOOST_AUTO_TEST_CASE(right)
-{
-    std::string value = "123   ";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("123", result);
-}
-
-BOOST_AUTO_TEST_CASE(both)
-{
-    std::string value = "   123   ";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("123", result);
-}
-
-BOOST_AUTO_TEST_CASE(none)
-{
-    std::string value = "without";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("without", result);
-}
-
-BOOST_AUTO_TEST_CASE(betweenEmpty)
-{
-    std::string value = "one list";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("one list", result);
-}
-
-BOOST_AUTO_TEST_CASE(betweenLeft)
-{
-    std::string value = "  space at left";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("space at left", result);
-}
-
-BOOST_AUTO_TEST_CASE(betweenRight)
-{
-    std::string value = "space at right  ";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("space at right", result);
-}
-
-BOOST_AUTO_TEST_CASE(betweenBoth)
-{
-    std::string value = "  space at both  ";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("space at both", result);
-}
-
-BOOST_AUTO_TEST_CASE(empty)
-{
-    std::string value = "    ";
-    std::string result = string_util::strip(value);
-
-    BOOST_REQUIRE_EQUAL("", result);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::join function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(join)
-
-BOOST_AUTO_TEST_CASE(empty)
-{
-    std::string expected = "";
-    std::string result = string_util::join<int>({});
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(one)
-{
-    std::string expected = "1";
-    std::string result = string_util::join({1});
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(two)
-{
-    std::string expected = "1:2";
-    std::string result = string_util::join({1, 2});
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(delimiterString)
-{
-    std::string expected = "1;;2;;3";
-    std::string result = string_util::join({1, 2, 3}, ";;");
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_CASE(delimiterChar)
-{
-    std::string expected = "1@2@3@4";
-    std::string result = string_util::join({1, 2, 3, 4}, '@');
-
-    BOOST_REQUIRE_EQUAL(expected, result);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::is_identifier function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(is_identifier_valid)
-
-BOOST_AUTO_TEST_CASE(correct)
-{
-    BOOST_REQUIRE(string_util::is_identifier("localhost"));
-    BOOST_REQUIRE(string_util::is_identifier("localhost2"));
-    BOOST_REQUIRE(string_util::is_identifier("localhost2-4_"));
-}
-
-BOOST_AUTO_TEST_CASE(incorrect)
-{
-    BOOST_REQUIRE(!string_util::is_identifier(""));
-    BOOST_REQUIRE(!string_util::is_identifier("localhost with spaces"));
-    BOOST_REQUIRE(!string_util::is_identifier("localhost*"));
-    BOOST_REQUIRE(!string_util::is_identifier("&&"));
-    BOOST_REQUIRE(!string_util::is_identifier("@'"));
-    BOOST_REQUIRE(!string_util::is_identifier("##"));
-    BOOST_REQUIRE(!string_util::is_identifier("===++"));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::is_boolean function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(is_boolean)
-
-BOOST_AUTO_TEST_CASE(correct)
-{
-    // true
-    BOOST_REQUIRE(string_util::is_boolean("true"));
-    BOOST_REQUIRE(string_util::is_boolean("True"));
-    BOOST_REQUIRE(string_util::is_boolean("TRUE"));
-    BOOST_REQUIRE(string_util::is_boolean("TruE"));
-
-    // yes
-    BOOST_REQUIRE(string_util::is_boolean("yes"));
-    BOOST_REQUIRE(string_util::is_boolean("Yes"));
-    BOOST_REQUIRE(string_util::is_boolean("YES"));
-    BOOST_REQUIRE(string_util::is_boolean("YeS"));
-
-    // on
-    BOOST_REQUIRE(string_util::is_boolean("on"));
-    BOOST_REQUIRE(string_util::is_boolean("On"));
-    BOOST_REQUIRE(string_util::is_boolean("oN"));
-    BOOST_REQUIRE(string_util::is_boolean("ON"));
-
-    // 1
-    BOOST_REQUIRE(string_util::is_boolean("1"));
-}
-
-BOOST_AUTO_TEST_CASE(incorrect)
-{
-    BOOST_REQUIRE(!string_util::is_boolean("false"));
-    BOOST_REQUIRE(!string_util::is_boolean("lol"));
-    BOOST_REQUIRE(!string_util::is_boolean(""));
-    BOOST_REQUIRE(!string_util::is_boolean("0"));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * string_util::is_number function
- * --------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(is_number)
-
-BOOST_AUTO_TEST_CASE(correct)
-{
-    BOOST_REQUIRE(string_util::is_number("123"));
-    BOOST_REQUIRE(string_util::is_number("-123"));
-    BOOST_REQUIRE(string_util::is_number("123.67"));
-}
-
-BOOST_AUTO_TEST_CASE(incorrect)
-{
-    BOOST_REQUIRE(!string_util::is_number("lol"));
-    BOOST_REQUIRE(!string_util::is_number("this is not a number"));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * fs_util::find function (name)
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(fs_find_name)
-
-BOOST_AUTO_TEST_CASE(not_recursive)
-{
-    auto file1 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-1.txt", false);
-    auto file2 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-2.txt", false);
-
-    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
-    BOOST_TEST(file2.empty());
-}
-
-BOOST_AUTO_TEST_CASE(recursive)
-{
-    auto file1 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-1.txt", true);
-    auto file2 = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", "file-2.txt", true);
-
-    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
-    BOOST_TEST(file2.find("file-2.txt") != std::string::npos);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * fs_util::find function (regex)
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(fs_find_regex)
-
-BOOST_AUTO_TEST_CASE(not_recursive)
-{
-    const std::regex regex("file-[12]\\.txt");
-
-    auto file = fs_util::find(CMAKE_SOURCE_DIR "/tests/root", regex, false);
-
-    BOOST_TEST(file.find("file-1.txt") != std::string::npos);
-}
-
-BOOST_AUTO_TEST_CASE(recursive)
-{
-    const std::regex regex("file-[12]\\.txt");
-
-    auto file = fs_util::find(CMAKE_SOURCE_DIR "/tests/root/level-1", regex, true);
-
-    BOOST_TEST(file.find("file-2.txt") != std::string::npos);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd