Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_di_container.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
16#include <gtest/gtest.h>
17
18#include <kcenon/common/di/service_container.h>
19
20#include <atomic>
21#include <chrono>
22#include <memory>
23#include <stdexcept>
24#include <string>
25#include <thread>
26#include <vector>
27
28using namespace kcenon::common::di;
29
33class IService {
34public:
35 virtual ~IService() = default;
36 virtual std::string get_name() const = 0;
37};
38
39class ServiceA : public IService {
40private:
41 static std::atomic<int> instance_count_;
42 int id_;
43
44public:
46 ~ServiceA() override { --instance_count_; }
47
48 std::string get_name() const override
49 {
50 return "ServiceA_" + std::to_string(id_);
51 }
52
53 int get_id() const { return id_; }
54
55 static int get_instance_count() { return instance_count_.load(); }
56
57 static void reset_count() { instance_count_ = 0; }
58};
59
60std::atomic<int> ServiceA::instance_count_{0};
61
62class ServiceB : public IService {
63private:
64 std::shared_ptr<ServiceA> service_a_;
65
66public:
67 explicit ServiceB(std::shared_ptr<ServiceA> a) : service_a_(std::move(a)) {}
68
69 std::string get_name() const override
70 {
71 return "ServiceB_with_" + service_a_->get_name();
72 }
73
74 std::shared_ptr<ServiceA> get_dependency() const { return service_a_; }
75};
76
80class DIContainerTest : public ::testing::Test {
81protected:
82 service_container container_;
83
84 void SetUp() override { ServiceA::reset_count(); }
85
86 void TearDown() override
87 {
88 container_.clear();
90 }
91};
92
96TEST_F(DIContainerTest, RegisterAndResolveTransient)
97{
98 auto result = container_.register_simple_factory<IService>(
99 []() { return std::make_shared<ServiceA>(); }, service_lifetime::transient);
100
101 ASSERT_TRUE(result.is_ok());
102 EXPECT_TRUE(container_.is_registered<IService>());
103
104 // Resolve service multiple times
105 auto service1_result = container_.resolve<IService>();
106 ASSERT_TRUE(service1_result.is_ok());
107 auto service1 = service1_result.value();
108 EXPECT_NE(service1, nullptr);
109 EXPECT_EQ(service1->get_name(), "ServiceA_1");
110
111 auto service2_result = container_.resolve<IService>();
112 ASSERT_TRUE(service2_result.is_ok());
113 auto service2 = service2_result.value();
114 EXPECT_NE(service2, nullptr);
115 EXPECT_EQ(service2->get_name(), "ServiceA_2");
116
117 // Transient services should be different instances
118 EXPECT_NE(service1, service2);
119 EXPECT_EQ(ServiceA::get_instance_count(), 2);
120}
121
125TEST_F(DIContainerTest, RegisterAndResolveSingleton)
126{
127 auto result = container_.register_simple_factory<IService>(
128 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
129
130 ASSERT_TRUE(result.is_ok());
131
132 // Resolve multiple times
133 auto service1_result = container_.resolve<IService>();
134 auto service2_result = container_.resolve<IService>();
135
136 ASSERT_TRUE(service1_result.is_ok());
137 ASSERT_TRUE(service2_result.is_ok());
138
139 auto service1 = service1_result.value();
140 auto service2 = service2_result.value();
141
142 // Singleton services should be the same instance
143 EXPECT_EQ(service1, service2);
144 EXPECT_EQ(ServiceA::get_instance_count(), 1);
145 EXPECT_EQ(service1->get_name(), "ServiceA_1");
146 EXPECT_EQ(service2->get_name(), "ServiceA_1");
147}
148
152TEST_F(DIContainerTest, RegisterSingletonInstance)
153{
154 auto instance = std::make_shared<ServiceA>();
155 auto initial_name = instance->get_name();
156
157 auto result = container_.register_instance<IService>(instance);
158 ASSERT_TRUE(result.is_ok());
159
160 // Resolve should return the same instance
161 auto resolved_result = container_.resolve<IService>();
162 ASSERT_TRUE(resolved_result.is_ok());
163 auto resolved = resolved_result.value();
164
165 EXPECT_EQ(resolved, instance);
166 EXPECT_EQ(resolved->get_name(), initial_name);
167}
168
172TEST_F(DIContainerTest, ServiceWithDependencies)
173{
174 // Register dependency as singleton
175 container_.register_simple_factory<ServiceA>(
176 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
177
178 // Register service that depends on ServiceA, using container to resolve
179 container_.register_factory<ServiceB>(
180 [](IServiceContainer& c) {
181 auto dep_result = c.resolve<ServiceA>();
182 if (dep_result.is_err()) {
183 throw std::runtime_error("Failed to resolve dependency");
184 }
185 return std::make_shared<ServiceB>(dep_result.value());
186 },
187 service_lifetime::transient);
188
189 // Resolve service with dependencies
190 auto service_result = container_.resolve<ServiceB>();
191 ASSERT_TRUE(service_result.is_ok());
192 auto service = service_result.value();
193
194 EXPECT_NE(service, nullptr);
195 auto dependency = service->get_dependency();
196 EXPECT_NE(dependency, nullptr);
197
198 // Dependency should be the same singleton
199 auto dep_result = container_.resolve<ServiceA>();
200 ASSERT_TRUE(dep_result.is_ok());
201 EXPECT_EQ(dependency, dep_result.value());
202}
203
207TEST_F(DIContainerTest, ScopedContainer)
208{
209 // Register singleton in root container
210 container_.register_simple_factory<IService>(
211 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
212
213 // Create scoped container
214 auto scope = container_.create_scope();
215 ASSERT_NE(scope, nullptr);
216
217 // Scoped container should inherit parent registrations
218 EXPECT_TRUE(scope->is_registered<IService>());
219
220 // Resolve in scope should work
221 auto service_result = scope->resolve<IService>();
222 ASSERT_TRUE(service_result.is_ok());
223 EXPECT_NE(service_result.value(), nullptr);
224}
225
229TEST_F(DIContainerTest, ResolveUnregisteredService)
230{
231 auto result = container_.resolve<IService>();
232 EXPECT_TRUE(result.is_err());
233}
234
238TEST_F(DIContainerTest, ResolveOrNullUnregistered)
239{
240 auto ptr = container_.resolve_or_null<IService>();
241 EXPECT_EQ(ptr, nullptr);
242}
243
247TEST_F(DIContainerTest, ClearContainer)
248{
249 container_.register_simple_factory<IService>(
250 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
251
252 EXPECT_TRUE(container_.is_registered<IService>());
253
254 container_.clear();
255
256 EXPECT_FALSE(container_.is_registered<IService>());
257
258 auto resolve_result = container_.resolve<IService>();
259 EXPECT_TRUE(resolve_result.is_err());
260}
261
265TEST_F(DIContainerTest, UnregisterService)
266{
267 container_.register_simple_factory<IService>(
268 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
269
270 EXPECT_TRUE(container_.is_registered<IService>());
271
272 auto result = container_.unregister<IService>();
273 EXPECT_TRUE(result.is_ok());
274 EXPECT_FALSE(container_.is_registered<IService>());
275}
276
280TEST_F(DIContainerTest, DuplicateRegistrationFails)
281{
282 auto result1 = container_.register_simple_factory<IService>(
283 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
284 ASSERT_TRUE(result1.is_ok());
285
286 auto result2 = container_.register_simple_factory<IService>(
287 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
288 EXPECT_TRUE(result2.is_err());
289}
290
295{
296 container_.register_simple_factory<IService>(
297 []() {
298 std::this_thread::sleep_for(std::chrono::milliseconds(1));
299 return std::make_shared<ServiceA>();
300 },
301 service_lifetime::singleton);
302
303 // Resolve from multiple threads
304 const int num_threads = 10;
305 std::vector<std::thread> threads;
306 std::vector<std::shared_ptr<IService>> results(num_threads);
307
308 for (int i = 0; i < num_threads; ++i) {
309 threads.emplace_back(
310 [this, &results, i]()
311 {
312 auto result = container_.resolve<IService>();
313 if (result.is_ok()) {
314 results[i] = result.value();
315 }
316 });
317 }
318
319 for (auto& t : threads) {
320 t.join();
321 }
322
323 // All threads should get the same singleton instance
324 auto first = results[0];
325 EXPECT_NE(first, nullptr);
326
327 for (const auto& result : results) {
328 EXPECT_EQ(result, first);
329 }
330
331 // Only one instance should have been created
332 EXPECT_EQ(ServiceA::get_instance_count(), 1);
333}
334
338TEST_F(DIContainerTest, RegisteredServicesDescriptors)
339{
340 container_.register_simple_factory<IService>(
341 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
342
343 container_.register_simple_factory<ServiceA>(
344 []() { return std::make_shared<ServiceA>(); }, service_lifetime::transient);
345
346 auto services = container_.registered_services();
347 EXPECT_GE(services.size(), 2u);
348}
349
353TEST_F(DIContainerTest, FreezePreventsRegistration)
354{
355 container_.register_simple_factory<IService>(
356 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
357
358 container_.freeze();
359 EXPECT_TRUE(container_.is_frozen());
360
361 auto result = container_.register_simple_factory<ServiceA>(
362 []() { return std::make_shared<ServiceA>(); }, service_lifetime::transient);
363
364 EXPECT_TRUE(result.is_err());
365}
366
370TEST_F(DIContainerTest, FreezeAllowsResolution)
371{
372 container_.register_simple_factory<IService>(
373 []() { return std::make_shared<ServiceA>(); }, service_lifetime::singleton);
374
375 container_.freeze();
376
377 auto result = container_.resolve<IService>();
378 EXPECT_TRUE(result.is_ok());
379 EXPECT_NE(result.value(), nullptr);
380}
void SetUp() override
service_container container_
void TearDown() override
virtual ~IService()=default
virtual std::string get_name() const =0
int get_id() const
std::string get_name() const override
static void reset_count()
static int get_instance_count()
~ServiceA() override
static std::atomic< int > instance_count_
ServiceB(std::shared_ptr< ServiceA > a)
std::shared_ptr< ServiceA > get_dependency() const
std::shared_ptr< ServiceA > service_a_
std::string get_name() const override
TEST_F(DIContainerTest, RegisterAndResolveTransient)