Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-29 08:25:01

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2026 Wouter Deconinck
0003 //
0004 // Tests for Configurable (PropertyMixin) and its self-registering Property<T>
0005 //
0006 #include <algorithms/property.h>
0007 
0008 #include <catch2/catch_test_macros.hpp>
0009 #include <catch2/matchers/catch_matchers_string.hpp>
0010 
0011 #include <fmt/format.h>
0012 #include <fmt/ranges.h>
0013 
0014 #include <cstdint>
0015 #include <ostream>
0016 #include <sstream>
0017 #include <stdexcept>
0018 #include <string>
0019 #include <variant>
0020 #include <vector>
0021 
0022 using namespace algorithms;
0023 
0024 namespace {
0025 
0026 enum class Mode { kFast, kSlow };
0027 
0028 class Config : public PropertyMixin {
0029 public:
0030   Property<bool> m_bool{this, "bool", true, "a bool"};
0031   Property<int> m_int{this, "int", 3, "an int"};
0032   Property<unsigned> m_uint{this, "uint", 4u, "an unsigned int"};
0033   Property<float> m_float{this, "float", 1.5f, "a float"};
0034   Property<double> m_double{this, "double", "a double without default"};
0035   Property<std::string> m_string{this, "string", "text", "a string"};
0036   Property<Mode> m_mode{this, "mode", Mode::kFast, "an enum"};
0037   Property<std::vector<int>> m_ints{this, "ints", {1, 2, 3}, "a vector of ints"};
0038   Property<std::vector<std::string>> m_strings{this, "strings", {"a", "b"}, "a vector of strings"};
0039 };
0040 
0041 } // namespace
0042 
0043 TEST_CASE("Properties register themselves with their owner", "[property]") {
0044   const Config c;
0045   const auto& props = c.getProperties();
0046 
0047   CHECK(props.size() == 9);
0048   CHECK(props.at("int").name() == "int");
0049   CHECK(props.at("int").description() == "an int");
0050   CHECK(props.at("double").description() == "a double without default");
0051 }
0052 
0053 TEST_CASE("Properties without default are missing until set", "[property]") {
0054   Config c;
0055 
0056   CHECK(c.hasProperty("int"));
0057   CHECK_FALSE(c.hasProperty("double"));
0058   CHECK_FALSE(c.hasProperty("unknown"));
0059   CHECK(c.missingProperties() == std::vector<std::string_view>{"double"});
0060   CHECK_THROWS_AS(c.validate(), PropertyError);
0061   CHECK_THROWS_WITH(c.validate(), R"(Missing properties: ["double"])");
0062   try {
0063     c.validate();
0064   } catch (const PropertyError& e) {
0065     CHECK(std::string{e.type()} == "algorithms::PropertyError");
0066   }
0067 
0068   c.setProperty("double", 2.5);
0069   CHECK(c.hasProperty("double"));
0070   CHECK(c.missingProperties().empty());
0071   CHECK_NOTHROW(c.validate());
0072 }
0073 
0074 TEST_CASE("Properties are set and retrieved through their upcast type", "[property]") {
0075   Config c;
0076 
0077   c.setProperty("bool", false);
0078   c.setProperty("int", short{5});
0079   c.setProperty("uint", uint8_t{6});
0080   c.setProperty("float", 2.5f);
0081   c.setProperty("double", 3.5);
0082   c.setProperty("string", "other");
0083   c.setProperty("mode", Mode::kSlow);
0084   c.setProperty("ints", std::vector<int>{4, 5});
0085   c.setProperty("strings", std::vector<std::string>{"c"});
0086 
0087   CHECK(c.getProperty<bool>("bool") == false);
0088   CHECK(c.getProperty<int64_t>("int") == 5);
0089   CHECK(c.getProperty<uint64_t>("uint") == 6);
0090   CHECK(c.getProperty<double>("float") == 2.5);
0091   CHECK(c.getProperty<double>("double") == 3.5);
0092   CHECK(c.getProperty<std::string>("string") == "other");
0093   CHECK(c.getProperty<int64_t>("mode") == static_cast<int64_t>(Mode::kSlow));
0094   CHECK(c.getProperty<std::vector<int>>("ints") == std::vector<int>{4, 5});
0095   CHECK(c.getProperty<std::vector<std::string>>("strings") == std::vector<std::string>{"c"});
0096 
0097   CHECK(c.m_bool == false);
0098   CHECK(c.m_int == 5);
0099   CHECK(c.m_uint == 6u);
0100   CHECK(c.m_float == 2.5f);
0101   CHECK(c.m_double == 3.5);
0102   CHECK(c.m_string == "other");
0103   CHECK(c.m_mode == Mode::kSlow);
0104 }
0105 
0106 TEST_CASE("Properties reject unknown names and wrong types", "[property]") {
0107   Config c;
0108 
0109   CHECK_THROWS_AS(c.setProperty("unknown", 1), std::out_of_range);
0110   CHECK_THROWS_AS(c.getProperty<int64_t>("unknown"), std::out_of_range);
0111   CHECK_THROWS_AS(c.setProperty("int", 1.5), std::bad_variant_access);
0112   CHECK_THROWS_AS(c.getProperty<int32_t>("int"), std::bad_variant_access);
0113 }
0114 
0115 TEST_CASE("Properties require a unique name and a valid owner", "[property]") {
0116   class Duplicate : public PropertyMixin {
0117     Property<int> m_a{this, "same", 1, ""};
0118     Property<int> m_b{this, "same", 2, ""};
0119   };
0120   CHECK_THROWS_WITH(Duplicate{}, "Duplicate property name: same");
0121   CHECK_THROWS_WITH((Configurable::Property<int>{nullptr, "orphan", "no owner"}),
0122                     "Attempting to create Property 'orphan' without valid owner");
0123 }
0124 
0125 TEST_CASE("Properties act as a const value", "[property]") {
0126   const Config c;
0127 
0128   const int i = c.m_int;
0129   CHECK(i == 3);
0130   CHECK(c.m_int.value() == 3);
0131 
0132   CHECK(c.m_int == 3);
0133   CHECK(3 == c.m_int);
0134   CHECK(c.m_int != 4);
0135   CHECK(c.m_int < 4);
0136   CHECK(c.m_int <= 3);
0137   CHECK(c.m_int > 2);
0138   CHECK(c.m_int >= 3);
0139   CHECK(c.m_int + 1 == 4);
0140   CHECK(c.m_int - 1 == 2);
0141   CHECK(c.m_int * 2 == 6);
0142   CHECK(c.m_int / 3 == 1);
0143   CHECK(c.m_float * 2.f == 3.f);
0144 }
0145 
0146 TEST_CASE("Properties can be printed and formatted", "[property]") {
0147   const Config c;
0148 
0149   std::ostringstream os;
0150   os << c.m_int << " " << c.m_string;
0151   CHECK(os.str() == "3 text");
0152 
0153   CHECK(fmt::format("{} {} {}", c.m_int, c.m_string, c.m_float) == "3 text 1.5");
0154   CHECK(fmt::format("{:>4}", c.m_int) == "   3");
0155   CHECK(fmt::format("{}", c.m_ints) == "[1, 2, 3]");
0156   CHECK(fmt::format("{}", c.m_strings) == R"(["a", "b"])");
0157 }
0158 
0159 namespace {
0160 template <class P>
0161 concept Collection = requires(const P& p) {
0162   p.begin();
0163   p.end();
0164   p.size();
0165   p.empty();
0166 };
0167 template <class P>
0168 concept Streamable = requires(std::ostream& os, const P& p) { os << p; };
0169 template <class T> using Prop = Configurable::Property<T>;
0170 } // namespace
0171 
0172 TEST_CASE("Properties only provide collection helpers for collections", "[property]") {
0173   STATIC_REQUIRE_FALSE(Collection<Prop<int>>);
0174   STATIC_REQUIRE_FALSE(Collection<Prop<double>>);
0175   STATIC_REQUIRE(Collection<Prop<std::string>>);
0176   STATIC_REQUIRE(Collection<Prop<std::vector<int>>>);
0177 
0178   STATIC_REQUIRE_FALSE(fmt::is_range<Prop<std::vector<int>>, char>::value);
0179 
0180   STATIC_REQUIRE(Streamable<Prop<int>>);
0181   STATIC_REQUIRE_FALSE(Streamable<Prop<Mode>>);
0182 }
0183 
0184 TEST_CASE("Properties of containers expose container access", "[property]") {
0185   Config c;
0186   const Config& cc = c;
0187 
0188   CHECK(cc.m_ints.size() == 3);
0189   CHECK_FALSE(cc.m_ints.empty());
0190   CHECK(cc.m_ints[1] == 2);
0191   CHECK(c.m_ints[2] == 3);
0192   CHECK(cc.m_string.length() == 4);
0193   CHECK(cc.m_string.find("xt") == 2);
0194   CHECK(cc.m_string[0] == 't');
0195   CHECK(std::vector<int>(cc.m_ints.begin(), cc.m_ints.end()) == std::vector<int>{1, 2, 3});
0196   CHECK(std::vector<int>(c.m_ints.begin(), c.m_ints.end()) == std::vector<int>{1, 2, 3});
0197 
0198   int sum = 0;
0199   for (int v : cc.m_ints) {
0200     sum += v;
0201   }
0202   CHECK(sum == 6);
0203 }
0204 
0205 TEST_CASE("Properties can be copied", "[property]") {
0206   const Config c;
0207   const auto copy{c.m_strings};
0208   CHECK(copy.value() == std::vector<std::string>{"a", "b"});
0209   CHECK(copy.name() == "strings");
0210   CHECK(copy.hasValue());
0211 }
0212 
0213 TEST_CASE("Properties are accessible through PropertyBase", "[property]") {
0214   Config c;
0215   auto& base = c.getProperties().at("uint");
0216 
0217   base.set(uint64_t{9});
0218   CHECK(c.m_uint == 9u);
0219   CHECK(std::get<uint64_t>(base.get()) == 9);
0220 }
0221 
0222 TEST_CASE("Properties can be set from lvalues", "[property]") {
0223   Config c;
0224   const int i = 7;
0225   float f     = 0.5f;
0226   std::string text{"lvalue"};
0227   const char* chars = "chars";
0228 
0229   c.setProperty("int", i);
0230   c.setProperty("float", f);
0231   c.setProperty("string", text);
0232   CHECK(c.m_int == 7);
0233   CHECK(c.m_float == 0.5f);
0234   CHECK(c.m_string == "lvalue");
0235 
0236   c.setProperty("string", chars);
0237   CHECK(c.m_string == "chars");
0238 }