Back to home page

EIC code displayed by LXR

 
 

    


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

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2026 Wouter Deconinck
0003 //
0004 // Tests for the ServiceSvc and the Service base class
0005 //
0006 // The ServiceSvc is a process-wide singleton that can only be initialized once, so the
0007 // full service lifecycle is tested in a single test case.
0008 //
0009 #include <algorithms/service.h>
0010 
0011 #include <catch2/catch_test_macros.hpp>
0012 #include <catch2/matchers/catch_matchers_string.hpp>
0013 
0014 #include <stdexcept>
0015 #include <string_view>
0016 
0017 using namespace algorithms;
0018 using Catch::Matchers::ContainsSubstring;
0019 
0020 namespace {
0021 
0022 // Counts how often it is initialized
0023 class CountingSvc : public Service<CountingSvc> {
0024 public:
0025   void init() { ++m_count; }
0026   int count() const { return m_count; }
0027 
0028 private:
0029   int m_count = 0;
0030 
0031   ALGORITHMS_DEFINE_SERVICE(CountingSvc)
0032 };
0033 
0034 // Fails to initialize on the first attempt
0035 class FlakySvc : public Service<FlakySvc> {
0036 public:
0037   void init() {
0038     if (m_fail) {
0039       m_fail = false;
0040       throw std::runtime_error("FlakySvc failed to initialize");
0041     }
0042   }
0043 
0044 private:
0045   bool m_fail = true;
0046 
0047   ALGORITHMS_DEFINE_SERVICE(FlakySvc)
0048 };
0049 
0050 // Has a property without default value
0051 class ConfiguredSvc : public Service<ConfiguredSvc> {
0052 public:
0053   void init() {}
0054 
0055 private:
0056   Property<int> m_required{this, "required", "required property without default"};
0057 
0058   ALGORITHMS_DEFINE_SERVICE(ConfiguredSvc)
0059 };
0060 
0061 // Needs a custom initializer
0062 class CustomInitSvc : public Service<CustomInitSvc> {
0063 public:
0064   void init() { m_value = -1; }
0065   void init(int value) { m_value = value; }
0066   int value() const { return m_value; }
0067 
0068 private:
0069   int m_value = 0;
0070 
0071   ALGORITHMS_DEFINE_SERVICE(CustomInitSvc)
0072 };
0073 
0074 // Instantiated only after ServiceSvc::init()
0075 class LateSvc : public Service<LateSvc> {
0076 public:
0077   void init() { ++m_count; }
0078   int count() const { return m_count; }
0079 
0080 private:
0081   int m_count = 0;
0082 
0083   ALGORITHMS_DEFINE_SERVICE(LateSvc)
0084 };
0085 
0086 // Instantiated only after ServiceSvc::init(), with a custom initializer
0087 class LateCustomInitSvc : public Service<LateCustomInitSvc> {
0088 public:
0089   void init() { m_value = -1; }
0090   void init(int value) { m_value = value; }
0091   int value() const { return m_value; }
0092 
0093 private:
0094   int m_value = 0;
0095 
0096   ALGORITHMS_DEFINE_SERVICE(LateCustomInitSvc)
0097 };
0098 
0099 // Not a Service<>, but added to the ServiceSvc by the framework
0100 class ExternalSvc : public ServiceBase {
0101 public:
0102   constexpr static const char* kName = "ExternalSvc";
0103   void init() { ++m_count; }
0104   int count() const { return m_count; }
0105 
0106 private:
0107   int m_count = 0;
0108 };
0109 
0110 // Never instantiated, so never registered
0111 class UnusedSvc : public Service<UnusedSvc> {
0112 public:
0113   void init() {}
0114 
0115   ALGORITHMS_DEFINE_SERVICE(UnusedSvc)
0116 };
0117 
0118 } // namespace
0119 
0120 TEST_CASE("ServiceSvc initializes all registered services", "[service]") {
0121   auto& svcs = ServiceSvc::instance();
0122   CHECK(svcs.name() == "ServiceSvc");
0123 
0124   // a custom initializer registered before the service is instantiated is kept
0125   svcs.setInit<CustomInitSvc>([](CustomInitSvc& s) { s.init(42); });
0126 
0127   // services register themselves when they are first accessed
0128   auto& counting   = CountingSvc::instance();
0129   auto& flaky      = FlakySvc::instance();
0130   auto& configured = ConfiguredSvc::instance();
0131   auto& custom     = CustomInitSvc::instance();
0132 
0133   CHECK(counting.name() == "CountingSvc");
0134   CHECK(counting.description() == "CountingSvc");
0135   CHECK(svcs.has<CountingSvc>());
0136   CHECK(svcs.has<FlakySvc>());
0137   CHECK(svcs.has<ConfiguredSvc>());
0138   CHECK(svcs.has<CustomInitSvc>());
0139   CHECK_FALSE(svcs.has<UnusedSvc>());
0140   CHECK(svcs.services().size() == 4);
0141   CHECK(svcs.service("CountingSvc") == &counting);
0142   CHECK(svcs.service<CountingSvc>("CountingSvc") == &counting);
0143   CHECK_THROWS_AS(svcs.service("UnusedSvc"), std::out_of_range);
0144 
0145   // nothing is ready before initialization
0146   CHECK_FALSE(svcs.ready());
0147   CHECK_FALSE(counting.ready());
0148   CHECK_THROWS_AS(svcs.initSingle<CountingSvc>(), ServiceError);
0149   CHECK_THROWS_WITH(svcs.initSingle<CountingSvc>(),
0150                     "initSingle<CountingSvc>() should not be called before/instead of init()");
0151 
0152   // initialization stops at the first service that throws
0153   CHECK_THROWS_WITH(svcs.init(), ContainsSubstring(R"(Encountered uninitialized services: ["FlakySvc"])"));
0154   CHECK(counting.ready());
0155   CHECK(counting.count() == 1);
0156   CHECK_FALSE(flaky.ready());
0157   CHECK_FALSE(svcs.ready());
0158 
0159   // initialization stops at the first service with missing properties
0160   CHECK_THROWS_WITH(
0161       svcs.init(),
0162       ContainsSubstring(R"(Encountered missing service properties: {"ConfiguredSvc": ["required"]})"));
0163   CHECK(counting.count() == 2);
0164   CHECK(flaky.ready());
0165   CHECK_FALSE(configured.ready());
0166   CHECK_FALSE(svcs.ready());
0167 
0168   // initialization succeeds once all properties are set
0169   configured.setProperty("required", 1);
0170   CHECK_NOTHROW(svcs.init());
0171   CHECK(svcs.ready());
0172   CHECK(counting.count() == 3);
0173   CHECK(configured.ready());
0174   CHECK(custom.ready());
0175   CHECK(custom.value() == 42);
0176   CHECK_NOTHROW(svcs.validate());
0177 
0178   // services can only be initialized once
0179   CHECK_THROWS_WITH(svcs.init(), "Cannot initialize services twice");
0180   CHECK_THROWS_WITH(svcs.initSingle<CountingSvc>(),
0181                     "Cannot initialize service CountingSvc - already initialized");
0182   CHECK(counting.count() == 3);
0183 
0184   // a single service that is no longer ready can be initialized again
0185   counting.ready(false);
0186   CHECK_FALSE(svcs.ready());
0187   CHECK_THROWS_WITH(svcs.validate(), ContainsSubstring(R"(Encountered uninitialized services: ["CountingSvc"])"));
0188   svcs.initSingle<CountingSvc>();
0189   CHECK(counting.ready());
0190   CHECK(counting.count() == 4);
0191   CHECK(svcs.ready());
0192 
0193   // services created after initialization are initialized when first accessed
0194   auto& late = LateSvc::instance();
0195   CHECK(svcs.has<LateSvc>());
0196   CHECK(late.ready());
0197   CHECK(late.count() == 1);
0198   CHECK(&LateSvc::instance() == &late);
0199   CHECK(late.count() == 1);
0200   CHECK(svcs.ready());
0201 
0202   // ... using their custom initializer if one was set before
0203   svcs.setInit<LateCustomInitSvc>([](LateCustomInitSvc& s) { s.init(7); });
0204   CHECK(LateCustomInitSvc::instance().ready());
0205   CHECK(LateCustomInitSvc::instance().value() == 7);
0206 
0207   // services added by the framework after initialization are initialized right away
0208   ExternalSvc external;
0209   svcs.add<ExternalSvc>(&external);
0210   CHECK(svcs.has<ExternalSvc>());
0211   CHECK(external.ready());
0212   CHECK(external.count() == 1);
0213   CHECK(svcs.ready());
0214 }