From 3429622d754a87ecca98b3fbde994f24e40d34b4 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Mon, 20 Apr 2020 20:10:25 +0300 Subject: Rewrite configuration tests in catch2 - Drop s_conf check in operator<< as s_conf cannot be nullptr there - Add arithmetic type cast to string values --- lib/configuration/configuration.cpp | 13 ++- lib/configuration/configuration.h | 54 ++++++---- lib/configuration/meson.build | 10 +- lib/configuration/qt_specialization.cpp | 10 +- lib/configuration/test/main.cpp | 174 ++++++++++++++++++++++++++++++++ lib/configuration/test/parser.cpp | 126 ----------------------- 6 files changed, 223 insertions(+), 164 deletions(-) create mode 100644 lib/configuration/test/main.cpp delete mode 100644 lib/configuration/test/parser.cpp diff --git a/lib/configuration/configuration.cpp b/lib/configuration/configuration.cpp index 0fdd0c0..01a5080 100644 --- a/lib/configuration/configuration.cpp +++ b/lib/configuration/configuration.cpp @@ -19,14 +19,16 @@ static std::unique_ptr s_conf; inline void 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()); } Configuration::Configuration() : use_global(true) { - if(!s_conf) - throw new std::runtime_error("Trying to use default Configuration, but none has been set!"); + if(!s_conf) { + throw std::runtime_error("Trying to use default Configuration, but none has been set!"); + } } Configuration::Configuration(std::initializer_list> l) noexcept @@ -58,8 +60,9 @@ void Configuration::read(std::basic_istream &input) } continue; } - if(line[0] == '#' || line.length() == 0) + if(line[0] == '#' || line.length() == 0) { continue; + } if(line.front() == '[' && line.back() == ']') { section = line.substr(1, line.length() - 2) + '/'; @@ -108,10 +111,6 @@ Configuration *Configuration::instance() std::ostream &operator<<(std::ostream &out, const Configuration &obj) { if(obj.use_global) { - if(!s_conf) { - throw new std::runtime_error("Trying to use default Configuration, but none has been set!"); - } - out << *s_conf; return out; } diff --git a/lib/configuration/configuration.h b/lib/configuration/configuration.h index 3d0a49d..9816ab7 100644 --- a/lib/configuration/configuration.h +++ b/lib/configuration/configuration.h @@ -17,7 +17,6 @@ #include #include #include -#include #if defined(__clang__) #define consumable(X) [[clang::consumable(X)]] @@ -41,9 +40,14 @@ class consumable(unconsumed) Configuration : private std::unordered_map> l) noexcept; - return_typestate(unconsumed) explicit Configuration(Configuration && other param_typestate(unconsumed)) = default; + return_typestate(unconsumed) Configuration(); + return_typestate(unconsumed) Configuration(std::initializer_list> l) noexcept; + + return_typestate(unconsumed) Configuration(const Configuration &other param_typestate(unconsumed)) = default; + Configuration &operator=(const Configuration &) = delete; + + return_typestate(unconsumed) Configuration(Configuration && other param_typestate(unconsumed)) = default; + Configuration &operator=(Configuration &&) = delete; ~Configuration() = default; @@ -53,11 +57,13 @@ public: template callable_when(unconsumed) [[nodiscard]] std::optional value(const char *path) const { - if(use_global) + if(use_global) { return instance()->value(path); + } - if(count(path) == 0) + if(count(path) == 0) { return std::nullopt; + } return std::get(at(path)); } @@ -65,8 +71,9 @@ public: template callable_when(unconsumed) [[nodiscard]] std::optional value(const char *path) const { - if(use_global) + if(use_global) { return instance()->value(path); + } if(this->count(path) == 0) { return std::nullopt; @@ -76,35 +83,46 @@ public: const auto value = at(path); if(std::holds_alternative(value)) { - if constexpr(std::is_arithmetic::value) + if constexpr(std::is_arithmetic::value) { return static_cast(std::get(value)); - else if constexpr(std::is_constructible::value) + } else if constexpr(std::is_constructible::value) { return T{ std::to_string(std::get(value)) }; + } } else if(std::holds_alternative(value)) { - if constexpr(std::is_constructible::value) + if constexpr(std::is_constructible::value) { return std::get(value); - else if constexpr(std::is_constructible::value) { - if(std::get(value)) - return T{ "true" }; - else - return T{ "false" }; + } else if constexpr(std::is_constructible::value) { + return std::get(value) ? T{ "true" } : T{ "false" }; } } else if(std::holds_alternative(value)) { auto str = std::get(value); - if(str.front() == '~') + + try { + 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)); + } + } catch(std::invalid_argument &) { + return std::nullopt; + } + + if(str.front() == '~') { str.replace(0, 1, m_homePath); + } - if constexpr(std::is_constructible::value) + if constexpr(std::is_constructible::value) { return T{ str }; + } } return std::nullopt; } template - callable_when(unconsumed) T &shortcut(T &, const char *) const + callable_when(unconsumed) T &shortcut(T & /* unused */, const char * /* unused */) const { return T{}; } diff --git a/lib/configuration/meson.build b/lib/configuration/meson.build index a0d915c..104f046 100644 --- a/lib/configuration/meson.build +++ b/lib/configuration/meson.build @@ -3,12 +3,12 @@ dep_configuration = declare_dependency( link_with: static_library('configuration', ['configuration.cpp', 'qt_specialization.cpp'], dependencies: dep_qt5) ) -test('configuration: parser', - executable('configuration-parser', - sources: [ 'test/parser.cpp' ], - dependencies: [ dep_qt5, dep_gtest, dep_configuration ] +test('configuration', executable('configuration-parser', + sources: [ 'test/main.cpp' ], + dependencies: [ dep_qt5, dep_catch, dep_configuration ] ), - env: environment({ 'CONFIGFILE' : meson.current_source_dir()/'test/defaultrc.ini' }), + env: 'CONFIGFILE='+meson.current_source_dir()/'test/defaultrc.ini', + args: [ '-platform', 'offscreen' ], workdir: meson.current_source_dir()/'test' ) diff --git a/lib/configuration/qt_specialization.cpp b/lib/configuration/qt_specialization.cpp index c58a498..8487e62 100644 --- a/lib/configuration/qt_specialization.cpp +++ b/lib/configuration/qt_specialization.cpp @@ -5,20 +5,14 @@ template <> callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const { const auto v = value(path); - if(!v) - return std::nullopt; - else - return QString::fromStdString(v.value()); + return v ? std::make_optional(QString::fromStdString(v.value())) : std::nullopt; } template <> callable_when(unconsumed) [[nodiscard]] std::optional Configuration::value(const char *path) const { const auto v = value(path); - if(!v) - return std::nullopt; - else - return QString::fromStdString(v.value()).split(';'); + return v ? std::make_optional(QString::fromStdString(v.value()).split(';')) : std::nullopt; } template <> diff --git a/lib/configuration/test/main.cpp b/lib/configuration/test/main.cpp new file mode 100644 index 0000000..21a02cf --- /dev/null +++ b/lib/configuration/test/main.cpp @@ -0,0 +1,174 @@ +#define CATCH_CONFIG_RUNNER + +#include "configuration.h" +#include +#include + +SCENARIO("Configuration") +{ + GIVEN("a Configuration object with some initial values") + { + Configuration conf{ + { "name", std::string() }, + { "over", std::string() }, + // this entry is not in the conf file + { "other", std::string("not in cfg") }, + // commented out entry in the conf file + { "comment", std::string("123.456") }, + { "number", int(0) }, + { "toggle", bool(false) }, + + { "main/name", std::string() }, + { "main/number", int(0) }, + { "main/toggle", bool(true) }, + + { "extra/name", std::string() }, + { "extra/number", int(0) }, + { "extra/toggle", bool(false) }, + }; + + WHEN("reading default values") + { + REQUIRE(conf.value("name")); + REQUIRE(conf.value("name").value() == std::string()); + REQUIRE(conf.value("number")); + REQUIRE(conf.value("number").value() == 0); + REQUIRE(conf.value("toggle")); + REQUIRE(conf.value("toggle").value() == false); + + REQUIRE(!conf.value("nullopt")); + REQUIRE(!conf.value("nullopt")); + REQUIRE(!conf.value("nullopt")); + + REQUIRE(conf.value("main/name")); + REQUIRE(conf.value("main/name").value() == std::string()); + REQUIRE(conf.value("main/number")); + REQUIRE(conf.value("main/number").value() == 0); + REQUIRE(conf.value("main/toggle")); + REQUIRE(conf.value("main/toggle").value() == true); + + REQUIRE(!conf.value("main/nullopt")); + REQUIRE(!conf.value("main/nullopt")); + REQUIRE(!conf.value("main/nullopt")); + + THEN("value casting") + { + REQUIRE(conf.value("number").value() == false); + REQUIRE(conf.value("number").value() == "0"); + + REQUIRE(conf.value("toggle").value() == "false"); + REQUIRE(conf.value("main/toggle").value() == "true"); + + REQUIRE(conf.value("comment").value() == 123); + REQUIRE(std::abs(conf.value("comment").value() - 123.456) < 0.001); + REQUIRE(!conf.value("name")); + } + } + + WHEN("reading configuration file") + { + conf.read_file(std::getenv("CONFIGFILE")); + + THEN("reading no section") + { + REQUIRE(conf.value("name").value() == "Top level"); + REQUIRE(conf.value("other").value() == "not in cfg"); + REQUIRE(conf.value("comment").value() == "123.456"); + REQUIRE(conf.value("number").value() == 12); + REQUIRE(conf.value("toggle").value() == true); + } + + THEN("reading main section") + { + REQUIRE(conf.value("main/name").value() == "Section Testing"); + REQUIRE(conf.value("main/number").value() == 10); + REQUIRE(conf.value("main/toggle").value() == false); + } + + THEN("reading included section") + { + REQUIRE(conf.value("over").value() == "extra"); + REQUIRE(conf.value("extra/name").value() == "extra section"); + REQUIRE(conf.value("extra/number").value() == 12); + REQUIRE(conf.value("extra/toggle").value() == true); + } + + THEN("value casting") + { + REQUIRE(conf.value("number").value() == "12"); + REQUIRE(conf.value("toggle").value() == "true"); + REQUIRE(conf.value("main/toggle").value() == "false"); + } + + THEN("Qt cast specialization") + { + REQUIRE(conf.value("name").value() == "Top level"); + REQUIRE(conf.value("number").value() == "12"); + REQUIRE(conf.value("toggle").value() == "true"); + REQUIRE(conf.value("main/toggle").value() == "false"); + REQUIRE(!conf.value("nullopt")); + + REQUIRE(conf.value("list").value() == QStringList({ "one", "two", "three", "for four" })); + REQUIRE(!conf.value("nullopt")); + } + + THEN("Qt shortcut") + { + REQUIRE(conf.value("qt/shortcut") == "Ctrl+Q"); + QAction action; + REQUIRE(conf.shortcut(action, "qt/shortcut").shortcut().toString() == "Ctrl+Q"); + REQUIRE(conf.shortcut(action, "qt/nil").shortcut().toString() == "Ctrl+Q"); + + QKeySequence sequence; + REQUIRE(conf.shortcut(sequence, "qt/shortcut").toString() == "Ctrl+Q"); + REQUIRE(conf.shortcut(sequence, "qt/nil").toString() == "Ctrl+Q"); + } + } + } + + GIVEN("global configuration") + { + std::unique_ptr global_conf = std::make_unique>>({ + { "name", std::string("global") }, + { "number", int(123) }, + { "toggle", bool(true) }, + + }); + + WHEN("no configuration is set") + { + REQUIRE_THROWS(Configuration()); + + std::stringstream output; + REQUIRE_THROWS(output << Configuration()); + } + WHEN("global instance is set") + { + std::stringstream output; + + output << *global_conf; + REQUIRE(output.str() == "name=global\nnumber=123\ntoggle=true\n"); + + Configuration::move_global(std::move(global_conf)); + Configuration g; + REQUIRE(g.value("name")); + REQUIRE(g.value("name").value() == "global"); + REQUIRE(g.value("number")); + REQUIRE(g.value("number").value() == 123); + REQUIRE(g.value("toggle")); + REQUIRE(g.value("toggle").value() == true); + REQUIRE(!g.value("nullopt")); + + output.str(std::string()); + output << g; + REQUIRE(output.str() == "name=global\nnumber=123\ntoggle=true\n"); + } + } +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + int result = Catch::Session().run(argc, argv); + return result; +} diff --git a/lib/configuration/test/parser.cpp b/lib/configuration/test/parser.cpp deleted file mode 100644 index 3ad64a5..0000000 --- a/lib/configuration/test/parser.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "configuration.h" -#include -#include -#include -#include -#include - -class ConfigurationTest : public ::testing::Test -{ -protected: - void SetUp() override - { - conf.read_file(std::getenv("CONFIGFILE")); - } - - Configuration conf{ - { "name", std::string() }, - { "over", std::string() }, - { "other", std::string("not in cfg") }, // this entry is not in the conf file - { "comment", std::string() }, // commented out entry in the conf file - { "number", int(0) }, - { "toggle", bool(false) }, - - { "main/name", std::string() }, - { "main/number", int(0) }, - { "main/toggle", bool(true) }, - - { "extra/name", std::string() }, - { "extra/number", int(0) }, - { "extra/toggle", bool(false) }, - }; - - std::unique_ptr global_conf = std::make_unique>>({ - { "name", std::string("global") }, - { "number", int(123) }, - { "toggle", bool(true) }, - - }); -}; - -TEST_F(ConfigurationTest, NoSection) -{ - EXPECT_EQ(conf.value("name").value(), "Top level"); - EXPECT_EQ(conf.value("other").value(), "not in cfg"); - EXPECT_EQ(conf.value("comment").value(), ""); - EXPECT_EQ(conf.value("number").value(), 12); - EXPECT_TRUE(conf.value("toggle").value()); - EXPECT_FALSE(conf.value("nullopt")); - EXPECT_FALSE(conf.value("nullopt")); - EXPECT_FALSE(conf.value("nullopt")); -} - -TEST_F(ConfigurationTest, TypeCasts) -{ - EXPECT_EQ(conf.value("number").value(), "12"); - EXPECT_EQ(conf.value("toggle").value(), "true"); - EXPECT_EQ(conf.value("main/toggle").value(), "false"); -} - -TEST_F(ConfigurationTest, QtSpecialization) -{ - EXPECT_EQ(conf.value("name").value(), "Top level"); - EXPECT_EQ(conf.value("number").value(), "12"); - EXPECT_EQ(conf.value("toggle").value(), "true"); - EXPECT_EQ(conf.value("main/toggle").value(), "false"); - EXPECT_FALSE(conf.value("nullopt")); - - EXPECT_EQ(conf.value("list").value(), QStringList({ "one", "two", "three", "for four" })); - EXPECT_FALSE(conf.value("nullopt")); -} - -TEST_F(ConfigurationTest, QtShortcut) -{ - QAction action; - EXPECT_EQ(conf.shortcut(action, "qt/shortcut").shortcut().toString(), "Ctrl+Q"); - EXPECT_EQ(conf.shortcut(action, "qt/nil").shortcut().toString(), "Ctrl+Q"); - - QKeySequence sequence; - EXPECT_EQ(conf.shortcut(sequence, "qt/shortcut").toString(), "Ctrl+Q"); - EXPECT_EQ(conf.shortcut(sequence, "qt/nil").toString(), "Ctrl+Q"); -} - -TEST_F(ConfigurationTest, MainSection) -{ - EXPECT_EQ(conf.value("main/name").value(), "Section Testing"); - EXPECT_EQ(conf.value("main/number").value(), 10); - EXPECT_FALSE(conf.value("main/toggle").value()); -} - -TEST_F(ConfigurationTest, ExtraSection) -{ - EXPECT_EQ(conf.value("over").value(), "extra"); - EXPECT_EQ(conf.value("extra/name").value(), "extra section"); - EXPECT_EQ(conf.value("extra/number").value(), 12); - EXPECT_TRUE(conf.value("extra/toggle").value()); -} - -TEST_F(ConfigurationTest, GlobalInstance) -{ - std::stringstream output; - - output << *global_conf; - EXPECT_EQ(output.str(), "name=global\nnumber=123\ntoggle=true\n") << "operator<< on global_conf before move"; - - Configuration::move_global(std::move(global_conf)); - Configuration g; - EXPECT_TRUE(g.value("name")); - EXPECT_EQ(g.value("name").value(), "global"); - EXPECT_TRUE(g.value("number")); - EXPECT_EQ(g.value("number").value(), 123); - EXPECT_TRUE(g.value("toggle")); - EXPECT_EQ(g.value("toggle").value(), true); - EXPECT_FALSE(g.value("nullopt")); - - output.str(std::string()); - output << g; - EXPECT_EQ(output.str(), "name=global\nnumber=123\ntoggle=true\n") << "operator<< on global_conf after move"; -} - -int main(int argc, char **argv) -{ - QApplication a(argc, argv); - - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -- cgit v1.2.1