aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAqua-sama <aqua@iserlohn-fortress.net>2020-04-20 20:10:25 +0300
committerAqua-sama <aqua@iserlohn-fortress.net>2020-04-20 22:08:20 +0300
commit3429622d754a87ecca98b3fbde994f24e40d34b4 (patch)
tree1e69a898fb2a364f4bb77afd3a9e1997d11cbd98
parentmove lib/about to src/about (diff)
downloadsmolbote-3429622d754a87ecca98b3fbde994f24e40d34b4.tar.xz
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
-rw-r--r--lib/configuration/configuration.cpp13
-rw-r--r--lib/configuration/configuration.h54
-rw-r--r--lib/configuration/meson.build10
-rw-r--r--lib/configuration/qt_specialization.cpp10
-rw-r--r--lib/configuration/test/main.cpp174
-rw-r--r--lib/configuration/test/parser.cpp126
6 files changed, 223 insertions, 164 deletions
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<Configuration> 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<char>(), ' ')));
+
value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to<char>(), ' ')).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<std::pair<std::string, conf_value_t>> l) noexcept
@@ -58,8 +60,9 @@ void Configuration::read(std::basic_istream<char> &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 <type_traits>
#include <unordered_map>
#include <variant>
-#include <vector>
#if defined(__clang__)
#define consumable(X) [[clang::consumable(X)]]
@@ -41,9 +40,14 @@ class consumable(unconsumed) Configuration : private std::unordered_map<std::str
friend std::ostream &operator<<(std::ostream &out, const Configuration &obj);
public:
- return_typestate(unconsumed) explicit Configuration();
- return_typestate(unconsumed) explicit Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> l) noexcept;
- return_typestate(unconsumed) explicit Configuration(Configuration && other param_typestate(unconsumed)) = default;
+ return_typestate(unconsumed) Configuration();
+ return_typestate(unconsumed) Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> 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 <typename T>
callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
{
- if(use_global)
+ if(use_global) {
return instance()->value<T>(path);
+ }
- if(count(path) == 0)
+ if(count(path) == 0) {
return std::nullopt;
+ }
return std::get<T>(at(path));
}
@@ -65,8 +71,9 @@ public:
template <concept_value_t T>
callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
{
- if(use_global)
+ if(use_global) {
return instance()->value<T>(path);
+ }
if(this->count(path) == 0) {
return std::nullopt;
@@ -76,35 +83,46 @@ public:
const auto value = at(path);
if(std::holds_alternative<int>(value)) {
- if constexpr(std::is_arithmetic<T>::value)
+ if constexpr(std::is_arithmetic<T>::value) {
return static_cast<T>(std::get<int>(value));
- else if constexpr(std::is_constructible<T, std::string>::value)
+ } else if constexpr(std::is_constructible<T, std::string>::value) {
return T{ std::to_string(std::get<int>(value)) };
+ }
} else if(std::holds_alternative<bool>(value)) {
- if constexpr(std::is_constructible<T, bool>::value)
+ if constexpr(std::is_constructible<T, bool>::value) {
return std::get<bool>(value);
- else if constexpr(std::is_constructible<T, const char *>::value) {
- if(std::get<bool>(value))
- return T{ "true" };
- else
- return T{ "false" };
+ } else if constexpr(std::is_constructible<T, const char *>::value) {
+ return std::get<bool>(value) ? T{ "true" } : T{ "false" };
}
} else if(std::holds_alternative<std::string>(value)) {
auto str = std::get<std::string>(value);
- if(str.front() == '~')
+
+ try {
+ if constexpr(std::is_floating_point<T>::value) {
+ return static_cast<T>(std::stod(str));
+ } else if constexpr(std::is_arithmetic<T>::value) {
+ return static_cast<T>(std::stol(str));
+ }
+ } catch(std::invalid_argument &) {
+ return std::nullopt;
+ }
+
+ if(str.front() == '~') {
str.replace(0, 1, m_homePath);
+ }
- if constexpr(std::is_constructible<T, std::string>::value)
+ if constexpr(std::is_constructible<T, std::string>::value) {
return T{ str };
+ }
}
return std::nullopt;
}
template <typename T>
- 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<QString> Configuration::value(const char *path) const
{
const auto v = value<std::string>(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<QStringList> Configuration::value(const char *path) const
{
const auto v = value<std::string>(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 <QApplication>
+#include <catch2/catch.hpp>
+
+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<std::string>("name"));
+ REQUIRE(conf.value<std::string>("name").value() == std::string());
+ REQUIRE(conf.value<int>("number"));
+ REQUIRE(conf.value<int>("number").value() == 0);
+ REQUIRE(conf.value<bool>("toggle"));
+ REQUIRE(conf.value<bool>("toggle").value() == false);
+
+ REQUIRE(!conf.value<int>("nullopt"));
+ REQUIRE(!conf.value<bool>("nullopt"));
+ REQUIRE(!conf.value<std::string>("nullopt"));
+
+ REQUIRE(conf.value<std::string>("main/name"));
+ REQUIRE(conf.value<std::string>("main/name").value() == std::string());
+ REQUIRE(conf.value<int>("main/number"));
+ REQUIRE(conf.value<int>("main/number").value() == 0);
+ REQUIRE(conf.value<bool>("main/toggle"));
+ REQUIRE(conf.value<bool>("main/toggle").value() == true);
+
+ REQUIRE(!conf.value<int>("main/nullopt"));
+ REQUIRE(!conf.value<bool>("main/nullopt"));
+ REQUIRE(!conf.value<std::string>("main/nullopt"));
+
+ THEN("value casting")
+ {
+ REQUIRE(conf.value<bool>("number").value() == false);
+ REQUIRE(conf.value<std::string>("number").value() == "0");
+
+ REQUIRE(conf.value<std::string>("toggle").value() == "false");
+ REQUIRE(conf.value<std::string>("main/toggle").value() == "true");
+
+ REQUIRE(conf.value<int>("comment").value() == 123);
+ REQUIRE(std::abs(conf.value<double>("comment").value() - 123.456) < 0.001);
+ REQUIRE(!conf.value<bool>("name"));
+ }
+ }
+
+ WHEN("reading configuration file")
+ {
+ conf.read_file(std::getenv("CONFIGFILE"));
+
+ THEN("reading no section")
+ {
+ REQUIRE(conf.value<std::string>("name").value() == "Top level");
+ REQUIRE(conf.value<std::string>("other").value() == "not in cfg");
+ REQUIRE(conf.value<std::string>("comment").value() == "123.456");
+ REQUIRE(conf.value<int>("number").value() == 12);
+ REQUIRE(conf.value<bool>("toggle").value() == true);
+ }
+
+ THEN("reading main section")
+ {
+ REQUIRE(conf.value<std::string>("main/name").value() == "Section Testing");
+ REQUIRE(conf.value<int>("main/number").value() == 10);
+ REQUIRE(conf.value<bool>("main/toggle").value() == false);
+ }
+
+ THEN("reading included section")
+ {
+ REQUIRE(conf.value<std::string>("over").value() == "extra");
+ REQUIRE(conf.value<std::string>("extra/name").value() == "extra section");
+ REQUIRE(conf.value<int>("extra/number").value() == 12);
+ REQUIRE(conf.value<bool>("extra/toggle").value() == true);
+ }
+
+ THEN("value casting")
+ {
+ REQUIRE(conf.value<std::string>("number").value() == "12");
+ REQUIRE(conf.value<std::string>("toggle").value() == "true");
+ REQUIRE(conf.value<std::string>("main/toggle").value() == "false");
+ }
+
+ THEN("Qt cast specialization")
+ {
+ REQUIRE(conf.value<QString>("name").value() == "Top level");
+ REQUIRE(conf.value<QString>("number").value() == "12");
+ REQUIRE(conf.value<QString>("toggle").value() == "true");
+ REQUIRE(conf.value<QString>("main/toggle").value() == "false");
+ REQUIRE(!conf.value<QString>("nullopt"));
+
+ REQUIRE(conf.value<QStringList>("list").value() == QStringList({ "one", "two", "three", "for four" }));
+ REQUIRE(!conf.value<QStringList>("nullopt"));
+ }
+
+ THEN("Qt shortcut")
+ {
+ REQUIRE(conf.value<std::string>("qt/shortcut") == "Ctrl+Q");
+ QAction action;
+ REQUIRE(conf.shortcut<QAction>(action, "qt/shortcut").shortcut().toString() == "Ctrl+Q");
+ REQUIRE(conf.shortcut<QAction>(action, "qt/nil").shortcut().toString() == "Ctrl+Q");
+
+ QKeySequence sequence;
+ REQUIRE(conf.shortcut<QKeySequence>(sequence, "qt/shortcut").toString() == "Ctrl+Q");
+ REQUIRE(conf.shortcut<QKeySequence>(sequence, "qt/nil").toString() == "Ctrl+Q");
+ }
+ }
+ }
+
+ GIVEN("global configuration")
+ {
+ std::unique_ptr<Configuration> global_conf = std::make_unique<Configuration, std::initializer_list<std::pair<std::string, conf_value_t>>>({
+ { "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<std::string>("name"));
+ REQUIRE(g.value<std::string>("name").value() == "global");
+ REQUIRE(g.value<int>("number"));
+ REQUIRE(g.value<int>("number").value() == 123);
+ REQUIRE(g.value<bool>("toggle"));
+ REQUIRE(g.value<bool>("toggle").value() == true);
+ REQUIRE(!g.value<std::string>("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 <QApplication>
-#include <cstdlib>
-#include <fstream>
-#include <gtest/gtest.h>
-#include <sstream>
-
-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<Configuration> global_conf = std::make_unique<Configuration, std::initializer_list<std::pair<std::string, conf_value_t>>>({
- { "name", std::string("global") },
- { "number", int(123) },
- { "toggle", bool(true) },
-
- });
-};
-
-TEST_F(ConfigurationTest, NoSection)
-{
- EXPECT_EQ(conf.value<std::string>("name").value(), "Top level");
- EXPECT_EQ(conf.value<std::string>("other").value(), "not in cfg");
- EXPECT_EQ(conf.value<std::string>("comment").value(), "");
- EXPECT_EQ(conf.value<int>("number").value(), 12);
- EXPECT_TRUE(conf.value<bool>("toggle").value());
- EXPECT_FALSE(conf.value<int>("nullopt"));
- EXPECT_FALSE(conf.value<bool>("nullopt"));
- EXPECT_FALSE(conf.value<std::string>("nullopt"));
-}
-
-TEST_F(ConfigurationTest, TypeCasts)
-{
- EXPECT_EQ(conf.value<std::string>("number").value(), "12");
- EXPECT_EQ(conf.value<std::string>("toggle").value(), "true");
- EXPECT_EQ(conf.value<std::string>("main/toggle").value(), "false");
-}
-
-TEST_F(ConfigurationTest, QtSpecialization)
-{
- EXPECT_EQ(conf.value<QString>("name").value(), "Top level");
- EXPECT_EQ(conf.value<QString>("number").value(), "12");
- EXPECT_EQ(conf.value<QString>("toggle").value(), "true");
- EXPECT_EQ(conf.value<QString>("main/toggle").value(), "false");
- EXPECT_FALSE(conf.value<QString>("nullopt"));
-
- EXPECT_EQ(conf.value<QStringList>("list").value(), QStringList({ "one", "two", "three", "for four" }));
- EXPECT_FALSE(conf.value<QStringList>("nullopt"));
-}
-
-TEST_F(ConfigurationTest, QtShortcut)
-{
- QAction action;
- EXPECT_EQ(conf.shortcut<QAction>(action, "qt/shortcut").shortcut().toString(), "Ctrl+Q");
- EXPECT_EQ(conf.shortcut<QAction>(action, "qt/nil").shortcut().toString(), "Ctrl+Q");
-
- QKeySequence sequence;
- EXPECT_EQ(conf.shortcut<QKeySequence>(sequence, "qt/shortcut").toString(), "Ctrl+Q");
- EXPECT_EQ(conf.shortcut<QKeySequence>(sequence, "qt/nil").toString(), "Ctrl+Q");
-}
-
-TEST_F(ConfigurationTest, MainSection)
-{
- EXPECT_EQ(conf.value<std::string>("main/name").value(), "Section Testing");
- EXPECT_EQ(conf.value<int>("main/number").value(), 10);
- EXPECT_FALSE(conf.value<bool>("main/toggle").value());
-}
-
-TEST_F(ConfigurationTest, ExtraSection)
-{
- EXPECT_EQ(conf.value<std::string>("over").value(), "extra");
- EXPECT_EQ(conf.value<std::string>("extra/name").value(), "extra section");
- EXPECT_EQ(conf.value<int>("extra/number").value(), 12);
- EXPECT_TRUE(conf.value<bool>("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<std::string>("name"));
- EXPECT_EQ(g.value<std::string>("name").value(), "global");
- EXPECT_TRUE(g.value<int>("number"));
- EXPECT_EQ(g.value<int>("number").value(), 123);
- EXPECT_TRUE(g.value<bool>("toggle"));
- EXPECT_EQ(g.value<bool>("toggle").value(), true);
- EXPECT_FALSE(g.value<std::string>("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();
-}