From fd87ed4d17dd8fc6fb7fbc936861393c6455815d Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 28 Apr 2024 17:46:14 +0300 Subject: Added libConfiguration unit test --- lib/configuration/CMakeLists.txt | 6 ++ lib/configuration/configuration.cpp | 118 +++++++++++++++++-------------- lib/configuration/configuration.h | 51 +++++++------ lib/configuration/qt_specialization.cpp | 31 ++++---- lib/configuration/qt_specialization.h | 8 +-- lib/configuration/test/configuration.cpp | 57 +++++++++++++++ 6 files changed, 173 insertions(+), 98 deletions(-) create mode 100644 lib/configuration/test/configuration.cpp (limited to 'lib') diff --git a/lib/configuration/CMakeLists.txt b/lib/configuration/CMakeLists.txt index 9e2f34e..7952eba 100644 --- a/lib/configuration/CMakeLists.txt +++ b/lib/configuration/CMakeLists.txt @@ -4,3 +4,9 @@ add_library(configuration STATIC ) target_link_libraries(configuration PUBLIC Qt6::Gui) target_include_directories(configuration PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(test_configuration test/configuration.cpp) +target_link_libraries(test_configuration PUBLIC configuration GTest::gtest_main) +gtest_add_tests(TARGET test_configuration + SOURCES test/configuration.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/test) diff --git a/lib/configuration/configuration.cpp b/lib/configuration/configuration.cpp index 75f863c..f2a2218 100644 --- a/lib/configuration/configuration.cpp +++ b/lib/configuration/configuration.cpp @@ -10,13 +10,15 @@ #include #include #include -#include #include +#include #ifndef NO_QT_SPEC #include #endif +using namespace std::placeholders; + static std::unique_ptr s_conf; Configuration::Configuration() @@ -27,66 +29,64 @@ Configuration::Configuration() } } -Configuration::Configuration(std::initializer_list> l) noexcept +Configuration::Configuration(std::initializer_list> p_list) noexcept #ifndef NO_QT_SPEC : m_homePath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString()) #endif { - for(const auto &i : l) { - insert_or_assign(i.first, i.second); + for(const auto &item : p_list) { + insert_or_assign(item); } } -void Configuration::read_file(const std::string &location) +bool Configuration::read_file(const std::string &location) { - std::fstream fs(location, std::fstream::in); - if(fs.is_open()) { - read(fs); - fs.close(); + std::fstream fstream(location, std::fstream::in); + bool result = false; + + if(fstream.is_open()) { + read(fstream); + fstream.close(); + result = true; } + + return result; } inline auto strip(std::string &value) { - value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to(), ' '))); - value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to(), ' ')).base(), value.end()); + // strip whitespace at the begining + value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind(std::not_equal_to<>(), ' ', _1))); + + // strip whitespace from the end + value.erase(std::find_if(value.rbegin(), value.rend(), std::bind(std::not_equal_to<>(), ' ', _1)).base(), value.end()); + return value; } -inline auto parse(const std::string &line, std::string §ion) +inline std::optional parse_line(const std::string &line, /* in/out */ std::string §ion) { - struct { - bool pair = false; - std::string key; - std::string value; - } ret; - - if(line[0] == '#' || line.length() == 0) { - return ret; - } - if(line.front() == '[' && line.back() == ']') { section = line.substr(1, line.length() - 2) + '/'; - return ret; + return std::nullopt; } const auto pos = line.find_first_of('='); if(pos == std::string::npos) { - return ret; + return std::nullopt; } - ret.key = line.substr(0, pos); - strip(ret.key); - if(ret.key.empty()) { - return ret; - } - ret.key = section + ret.key; + std::string key = line.substr(0, pos); + std::string val = line.substr(pos + 1); + strip(key); + strip(val); - ret.value = line.substr(pos + 1); - strip(ret.value); + if(key.empty()) { + return std::nullopt; + } + key = section + key; - ret.pair = true; - return ret; + return std::make_optional({ key, val }); } #ifdef FUZZER @@ -100,9 +100,16 @@ extern "C" int LLVMFuzzerTestOneInput(const char *Data, long long Size) void Configuration::read(std::basic_istream &input) { - std::string line, section; + std::string line; + std::string section; while(std::getline(input, line)) { + // skip empty lines and comments + if(line.length() == 0 || line[0] == '#') { + continue; + } + + // include file if(line.rfind("@@") == 0) { if(line.rfind("@@include ") == 0) { read_file(line.substr(10)); @@ -110,23 +117,30 @@ void Configuration::read(std::basic_istream &input) continue; } - const auto pair = parse(line, section); - if(pair.pair) { + const auto pair = parse_line(line, section); + if(pair) { + insert_or_assign(*pair); + } + } +} - if(this->count(pair.key) == 0) { - // no type has been specified for this key, assuming std::string - insert(std::make_pair(pair.key, pair.value)); - continue; - } +void Configuration::insert_or_assign(const Configuration::kv_pair &pair) +{ + // insert + if(count(pair.first) == 0) { + insert(pair); + } else { + // when assigning we have to keep the same type + const auto old_value = at(pair.first); - auto v = at(pair.key); - if(std::holds_alternative(v)) { - at(pair.key) = pair.value; - } else if(std::holds_alternative(v)) { - at(pair.key) = std::stoi(pair.value); - } else if(std::holds_alternative(v)) { - at(pair.key) = (pair.value == "true"); - } + if(std::holds_alternative(old_value)) { + at(pair.first) = pair.second; + + } else if(std::holds_alternative(old_value)) { + at(pair.first) = std::stoi(std::get(pair.second)); + + } else if(std::holds_alternative(old_value)) { + at(pair.first) = (std::get(pair.second) == "true"); } } } @@ -150,9 +164,7 @@ std::ostream &operator<<(std::ostream &out, const Configuration &obj) // unordered_map is, well, unordered, so grab the keys and sort them before printing them std::vector keys; - for(const auto &pair : obj) { - keys.emplace_back(pair.first); - } + std::transform(obj.cbegin(), obj.cend(), std::inserter(keys, keys.end()), [](const auto &pair) { return pair.first; }); std::sort(keys.begin(), keys.end()); for(const auto &key : keys) { diff --git a/lib/configuration/configuration.h b/lib/configuration/configuration.h index cd3c244..30f258c 100644 --- a/lib/configuration/configuration.h +++ b/lib/configuration/configuration.h @@ -17,44 +17,34 @@ #include #include -#if defined(__clang__) -#define consumable(X) [[clang::consumable(X)]] -#define return_typestate(X) [[clang::return_typestate(X)]] -#define callable_when(X) [[clang::callable_when(X)]] -#define param_typestate(X) [[clang::param_typestate(X)]] -#else -#define consumable(X) -#define return_typestate(X) -#define callable_when(X) -#define param_typestate(X) -#endif - -typedef std::variant conf_value_t; +using conf_value_t = std::variant; template -concept concept_value_t = std::is_arithmetic::value || std::is_same::value || std::is_constructible::value; +concept concept_value_t = std::is_arithmetic_v || std::is_same_v || std::is_constructible_v; -class consumable(unconsumed) Configuration : private std::unordered_map +class Configuration : private std::unordered_map { friend std::ostream &operator<<(std::ostream &out, const Configuration &obj); public: - return_typestate(unconsumed) Configuration(); - return_typestate(unconsumed) Configuration(std::initializer_list> l) noexcept; + using kv_pair = std::pair; + + Configuration(); + Configuration(std::initializer_list> p_list) noexcept; Configuration(const Configuration &) = delete; Configuration &operator=(const Configuration &) = delete; - return_typestate(unconsumed) Configuration(Configuration && other param_typestate(unconsumed)) = default; + Configuration(Configuration &&other) = default; Configuration &operator=(Configuration &&) = delete; ~Configuration() = default; - callable_when(unconsumed) void read_file(const std::string &location); - callable_when(unconsumed) void read(std::basic_istream & input); + bool read_file(const std::string &location); + void read(std::basic_istream &input); template - callable_when(unconsumed) [[nodiscard]] std::optional value(const char *path) const + [[nodiscard]] std::optional value(const char *path) const { if(use_global) { return instance()->value(path); @@ -68,18 +58,18 @@ public: } template - callable_when(unconsumed) [[nodiscard]] std::optional value(const char *path) const + [[nodiscard]] std::optional value(const char *p_path) const { if(use_global) { - return instance()->value(path); + return instance()->value(p_path); } - if(this->count(path) == 0) { + if(this->count(p_path) == 0) { return std::nullopt; } // path is guaranteed to exist - const auto value = at(path); + const auto value = at(p_path); if(std::holds_alternative(value)) { if constexpr(std::is_arithmetic::value) { @@ -91,6 +81,8 @@ public: } else if(std::holds_alternative(value)) { if constexpr(std::is_constructible::value) { return std::get(value); + } else if constexpr(std::is_arithmetic_v) { + return static_cast(std::get(value)); } else if constexpr(std::is_constructible::value) { return std::get(value) ? T{ "true" } : T{ "false" }; } @@ -99,7 +91,9 @@ public: auto str = std::get(value); try { - if constexpr(std::is_floating_point::value) { + if constexpr(std::is_same_v) { + return (str == "true"); + } else if constexpr(std::is_floating_point::value) { return static_cast(std::stod(str)); } else if constexpr(std::is_arithmetic::value) { return static_cast(std::stol(str)); @@ -121,13 +115,16 @@ public: } template - callable_when(unconsumed) T &shortcut(T & /* unused */, const char * /* unused */) const + T &shortcut(T & /* unused */, const char * /* unused */) const { return T{}; } static void move_global(std::unique_ptr && conf); +protected: + void insert_or_assign(const kv_pair &pair); + private: static Configuration *instance(); diff --git a/lib/configuration/qt_specialization.cpp b/lib/configuration/qt_specialization.cpp index 8487e62..a97ed2b 100644 --- a/lib/configuration/qt_specialization.cpp +++ b/lib/configuration/qt_specialization.cpp @@ -1,36 +1,39 @@ #include "configuration.h" -#include template <> -callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const +[[nodiscard]] std::optional Configuration::value(const char *path) const { - const auto v = value(path); - return v ? std::make_optional(QString::fromStdString(v.value())) : std::nullopt; + const auto result = value(path); + return result ? std::make_optional(QString::fromStdString(result.value())) : std::nullopt; } template <> -callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const +[[nodiscard]] std::optional Configuration::value(const char *path) const { - const auto v = value(path); - return v ? std::make_optional(QString::fromStdString(v.value()).split(';')) : std::nullopt; + const auto result = value(path); + return result ? std::make_optional(QString::fromStdString(result.value()).split(';')) : std::nullopt; } template <> -callable_when(unconsumed) QAction &Configuration::shortcut(QAction &action, const char *name) const +QAction &Configuration::shortcut(QAction &action, const char *name) const { - if(const auto shortcut = value(name)) { + if(const auto result = value(name)) { const QString old_tooltip = action.toolTip(); - action.setShortcut(QKeySequence::fromString(shortcut.value())); - action.setToolTip(QString("%1 (%2)").arg(old_tooltip, shortcut.value())); + const auto &result_value = result.value(); + + action.setShortcut(QKeySequence::fromString(result_value)); + action.setToolTip(QString("%1 (%2)").arg(old_tooltip, result_value)); } return action; } template <> -callable_when(unconsumed) QKeySequence &Configuration::shortcut(QKeySequence &sequence, const char *name) const +QKeySequence &Configuration::shortcut(QKeySequence &sequence, const char *name) const { - if(const auto shortcut = value(name)) { - sequence = QKeySequence::fromString(shortcut.value()); + if(const auto result = value(name)) { + const auto &result_value = result.value(); + + sequence = QKeySequence::fromString(result_value); } return sequence; } diff --git a/lib/configuration/qt_specialization.h b/lib/configuration/qt_specialization.h index 9634261..af8466e 100644 --- a/lib/configuration/qt_specialization.h +++ b/lib/configuration/qt_specialization.h @@ -5,11 +5,11 @@ #include template <> -callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const; +[[nodiscard]] std::optional Configuration::value(const char *path) const; template <> -callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const; +[[nodiscard]] std::optional Configuration::value(const char *path) const; template <> -callable_when(unconsumed) QAction &Configuration::shortcut(QAction &, const char *) const; +QAction &Configuration::shortcut(QAction &, const char *) const; template <> -callable_when(unconsumed) QKeySequence &Configuration::shortcut(QKeySequence &, const char *) const; +QKeySequence &Configuration::shortcut(QKeySequence &, const char *) const; diff --git a/lib/configuration/test/configuration.cpp b/lib/configuration/test/configuration.cpp new file mode 100644 index 0000000..0796c11 --- /dev/null +++ b/lib/configuration/test/configuration.cpp @@ -0,0 +1,57 @@ +#include "configuration.h" +#include + +TEST(Configuration, ctor_empty_initializer_list) +{ + Configuration conf({}); + + std::cout << conf; +} + +TEST(Configuration, ctor_initializer_list) +{ + Configuration conf({ + { "some_string", "value1" }, + { "some_bool", true }, + { "some_int", 42 }, + }); + + // operator<< should not throw exceptions + std::cout << conf; + + EXPECT_EQ(*conf.value("some_string"), "value1"); + EXPECT_EQ(*conf.value("some_bool"), true); + EXPECT_EQ(*conf.value("some_int"), 42); + + EXPECT_FALSE(conf.value("no_int")); +} + +TEST(Configuration, read_file) +{ + // because the init_list is empty, all types are strings + Configuration conf({}); + + EXPECT_TRUE(conf.read_file("defaultrc.ini")); + + std::cout << "---------\n"; + std::cout << conf; + std::cout << "---------\n"; + + // defaultrc.ini contents + EXPECT_EQ(*conf.value("name"), "Top level"); + EXPECT_FALSE(conf.value("comment")); + + EXPECT_EQ(*conf.value("main/name"), "Section Testing"); + + // casting a string to an integer + EXPECT_EQ(*conf.value("number"), 12); + + // casting a string to a boolean + EXPECT_EQ(*conf.value("toggle"), "true"); + EXPECT_EQ(*conf.value("toggle"), true); + EXPECT_EQ(*conf.value("main/toggle"), false); + + // extrarc.ini contents + EXPECT_EQ(*conf.value("over"), "extra"); + EXPECT_EQ(*conf.value("extra/name"), "extra section"); +} -- cgit v1.2.1