Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
backend_registry_test.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
19#include <algorithm>
20#include <atomic>
21#include <gtest/gtest.h>
22#include <memory>
23#include <string>
24#include <thread>
25#include <vector>
26
30
31using namespace database;
32using namespace database::core;
33
34// =============================================================================
35// Test Backend Implementations
36// =============================================================================
37
38namespace {
39
46class test_backend
47 : public backend_base<test_backend, database_types::sqlite>
48{
49public:
50 static constexpr const char* backend_name() { return "test_backend"; }
51
52 kcenon::common::Result<database_result> select_query(const std::string&) override
53 {
54 return kcenon::common::Result<database_result>::ok(database_result{});
55 }
56
57 kcenon::common::VoidResult execute_query(const std::string&) override
58 {
59 return kcenon::common::ok();
60 }
61
62 kcenon::common::VoidResult begin_transaction() override
63 {
64 return kcenon::common::ok();
65 }
66
67 kcenon::common::VoidResult commit_transaction() override
68 {
69 return kcenon::common::ok();
70 }
71
72 kcenon::common::VoidResult rollback_transaction() override
73 {
74 return kcenon::common::ok();
75 }
76
77 bool in_transaction() const override { return false; }
78 std::string last_error() const override { return ""; }
79 std::map<std::string, std::string> connection_info() const override { return {}; }
80
81protected:
82 friend class backend_base<test_backend, database_types::sqlite>;
83
84 kcenon::common::VoidResult do_initialize(const connection_config&)
85 {
86 return kcenon::common::ok();
87 }
88
89 kcenon::common::VoidResult do_shutdown()
90 {
91 return kcenon::common::ok();
92 }
93};
94
98class test_backend_alt
99 : public backend_base<test_backend_alt, database_types::postgres>
100{
101public:
102 static constexpr const char* backend_name() { return "test_backend_alt"; }
103
104 kcenon::common::Result<database_result> select_query(const std::string&) override
105 {
106 return kcenon::common::Result<database_result>::ok(database_result{});
107 }
108
109 kcenon::common::VoidResult execute_query(const std::string&) override
110 {
111 return kcenon::common::ok();
112 }
113
114 kcenon::common::VoidResult begin_transaction() override
115 {
116 return kcenon::common::ok();
117 }
118
119 kcenon::common::VoidResult commit_transaction() override
120 {
121 return kcenon::common::ok();
122 }
123
124 kcenon::common::VoidResult rollback_transaction() override
125 {
126 return kcenon::common::ok();
127 }
128
129 bool in_transaction() const override { return false; }
130 std::string last_error() const override { return ""; }
131 std::map<std::string, std::string> connection_info() const override { return {}; }
132
133protected:
134 friend class backend_base<test_backend_alt, database_types::postgres>;
135
136 kcenon::common::VoidResult do_initialize(const connection_config&)
137 {
138 return kcenon::common::ok();
139 }
140
141 kcenon::common::VoidResult do_shutdown()
142 {
143 return kcenon::common::ok();
144 }
145};
146
147} // anonymous namespace
148
149// =============================================================================
150// Test Fixture
151// =============================================================================
152
153class BackendRegistryTest : public ::testing::Test {
154protected:
155 void SetUp() override
156 {
157 // Clear registry before each test to ensure isolation
159 }
160
161 void TearDown() override
162 {
164 }
165};
166
167// =============================================================================
168// Registration Tests
169// =============================================================================
170
171TEST_F(BackendRegistryTest, RegisterBackendSucceeds)
172{
174 "test", &test_backend::create);
175 EXPECT_TRUE(result.is_ok());
176}
177
178TEST_F(BackendRegistryTest, RegisterMultipleBackends)
179{
180 EXPECT_TRUE(backend_registry::instance()
181 .register_backend("test1", &test_backend::create).is_ok());
182 EXPECT_TRUE(backend_registry::instance()
183 .register_backend("test2", &test_backend_alt::create).is_ok());
184 EXPECT_EQ(backend_registry::instance().backend_count(), 2u);
185}
186
187TEST_F(BackendRegistryTest, DuplicateRegistrationFails)
188{
189 EXPECT_TRUE(backend_registry::instance()
190 .register_backend("test", &test_backend::create).is_ok());
191 auto result = backend_registry::instance()
192 .register_backend("test", &test_backend::create);
193 EXPECT_FALSE(result.is_ok());
194}
195
196TEST_F(BackendRegistryTest, UnregisterExistingBackend)
197{
198 backend_registry::instance().register_backend("test", &test_backend::create);
199 auto result = backend_registry::instance().unregister_backend("test");
200 EXPECT_TRUE(result.is_ok());
201 EXPECT_FALSE(backend_registry::instance().has_backend("test"));
202}
203
204TEST_F(BackendRegistryTest, UnregisterNonExistentBackendFails)
205{
206 auto result = backend_registry::instance().unregister_backend("nonexistent");
207 EXPECT_FALSE(result.is_ok());
208}
209
210// =============================================================================
211// Creation Tests
212// =============================================================================
213
214TEST_F(BackendRegistryTest, CreateRegisteredBackend)
215{
216 backend_registry::instance().register_backend("test", &test_backend::create);
217 auto backend = backend_registry::instance().create("test");
218 ASSERT_NE(backend, nullptr);
219 EXPECT_EQ(backend->type(), database_types::sqlite);
220}
221
222TEST_F(BackendRegistryTest, CreateUnregisteredBackendReturnsNull)
223{
224 auto backend = backend_registry::instance().create("nonexistent");
225 EXPECT_EQ(backend, nullptr);
226}
227
228TEST_F(BackendRegistryTest, CreateReturnsDistinctInstances)
229{
230 backend_registry::instance().register_backend("test", &test_backend::create);
231 auto b1 = backend_registry::instance().create("test");
232 auto b2 = backend_registry::instance().create("test");
233 ASSERT_NE(b1, nullptr);
234 ASSERT_NE(b2, nullptr);
235 EXPECT_NE(b1.get(), b2.get());
236}
237
238TEST_F(BackendRegistryTest, CreatedBackendIsNotInitialized)
239{
240 backend_registry::instance().register_backend("test", &test_backend::create);
241 auto backend = backend_registry::instance().create("test");
242 ASSERT_NE(backend, nullptr);
243 EXPECT_FALSE(backend->is_initialized());
244}
245
246// =============================================================================
247// Query Tests
248// =============================================================================
249
250TEST_F(BackendRegistryTest, HasBackendReturnsTrueForRegistered)
251{
252 backend_registry::instance().register_backend("test", &test_backend::create);
253 EXPECT_TRUE(backend_registry::instance().has_backend("test"));
254}
255
256TEST_F(BackendRegistryTest, HasBackendReturnsFalseForUnregistered)
257{
258 EXPECT_FALSE(backend_registry::instance().has_backend("nonexistent"));
259}
260
261TEST_F(BackendRegistryTest, AvailableBackendsListsAll)
262{
263 backend_registry::instance().register_backend("alpha", &test_backend::create);
264 backend_registry::instance().register_backend("beta", &test_backend_alt::create);
265
267 EXPECT_EQ(backends.size(), 2u);
268 EXPECT_TRUE(std::find(backends.begin(), backends.end(), "alpha") != backends.end());
269 EXPECT_TRUE(std::find(backends.begin(), backends.end(), "beta") != backends.end());
270}
271
272TEST_F(BackendRegistryTest, BackendCountMatchesRegistrations)
273{
274 EXPECT_EQ(backend_registry::instance().backend_count(), 0u);
275 backend_registry::instance().register_backend("a", &test_backend::create);
276 EXPECT_EQ(backend_registry::instance().backend_count(), 1u);
277 backend_registry::instance().register_backend("b", &test_backend_alt::create);
278 EXPECT_EQ(backend_registry::instance().backend_count(), 2u);
279}
280
281TEST_F(BackendRegistryTest, EmptyRegistryHasZeroCount)
282{
283 EXPECT_EQ(backend_registry::instance().backend_count(), 0u);
284 EXPECT_TRUE(backend_registry::instance().available_backends().empty());
285}
286
287// =============================================================================
288// Clear Tests
289// =============================================================================
290
291TEST_F(BackendRegistryTest, ClearRemovesAllBackends)
292{
293 backend_registry::instance().register_backend("a", &test_backend::create);
294 backend_registry::instance().register_backend("b", &test_backend_alt::create);
295 EXPECT_EQ(backend_registry::instance().backend_count(), 2u);
296
298 EXPECT_EQ(backend_registry::instance().backend_count(), 0u);
299 EXPECT_FALSE(backend_registry::instance().has_backend("a"));
300 EXPECT_FALSE(backend_registry::instance().has_backend("b"));
301}
302
303TEST_F(BackendRegistryTest, ReRegisterAfterClear)
304{
305 backend_registry::instance().register_backend("test", &test_backend::create);
307
309 "test", &test_backend::create);
310 EXPECT_TRUE(result.is_ok());
311 EXPECT_TRUE(backend_registry::instance().has_backend("test"));
312}
313
314// =============================================================================
315// Convenience Function Tests
316// =============================================================================
317
318TEST_F(BackendRegistryTest, ConvenienceCreateBackend)
319{
320 backend_registry::instance().register_backend("test", &test_backend::create);
321 auto backend = create_backend("test");
322 ASSERT_NE(backend, nullptr);
323 EXPECT_EQ(backend->type(), database_types::sqlite);
324}
325
326TEST_F(BackendRegistryTest, ConvenienceHasBackend)
327{
328 backend_registry::instance().register_backend("test", &test_backend::create);
329 EXPECT_TRUE(has_backend("test"));
330 EXPECT_FALSE(has_backend("nonexistent"));
331}
332
333TEST_F(BackendRegistryTest, ConvenienceAvailableBackends)
334{
335 backend_registry::instance().register_backend("x", &test_backend::create);
336 auto backends = available_backends();
337 EXPECT_EQ(backends.size(), 1u);
338 EXPECT_EQ(backends[0], "x");
339}
340
341// =============================================================================
342// Thread Safety Tests
343// =============================================================================
344
345TEST_F(BackendRegistryTest, ConcurrentRegistration)
346{
347 constexpr int num_threads = 10;
348 std::atomic<int> success_count{0};
349 std::vector<std::thread> threads;
350
351 for (int i = 0; i < num_threads; ++i) {
352 threads.emplace_back([i, &success_count]() {
354 "backend_" + std::to_string(i), &test_backend::create);
355 if (result.is_ok()) {
356 success_count++;
357 }
358 });
359 }
360
361 for (auto& t : threads) {
362 t.join();
363 }
364
365 EXPECT_EQ(success_count.load(), num_threads);
366 EXPECT_EQ(backend_registry::instance().backend_count(),
367 static_cast<size_t>(num_threads));
368}
369
370TEST_F(BackendRegistryTest, ConcurrentCreation)
371{
372 backend_registry::instance().register_backend("test", &test_backend::create);
373
374 constexpr int num_threads = 20;
375 std::atomic<int> success_count{0};
376 std::vector<std::thread> threads;
377
378 for (int i = 0; i < num_threads; ++i) {
379 threads.emplace_back([&success_count]() {
380 auto backend = backend_registry::instance().create("test");
381 if (backend != nullptr) {
382 success_count++;
383 }
384 });
385 }
386
387 for (auto& t : threads) {
388 t.join();
389 }
390
391 EXPECT_EQ(success_count.load(), num_threads);
392}
393
394TEST_F(BackendRegistryTest, ConcurrentRegistrationAndQuery)
395{
396 constexpr int num_threads = 10;
397 std::vector<std::thread> threads;
398
399 // Register in parallel
400 for (int i = 0; i < num_threads; ++i) {
401 threads.emplace_back([i]() {
403 "backend_" + std::to_string(i), &test_backend::create);
404 });
405 }
406
407 // Query in parallel at the same time
408 for (int i = 0; i < num_threads; ++i) {
409 threads.emplace_back([]() {
410 // These may or may not find backends depending on timing
413 });
414 }
415
416 for (auto& t : threads) {
417 t.join();
418 }
419
420 // All registrations should eventually succeed
421 EXPECT_EQ(backend_registry::instance().backend_count(),
422 static_cast<size_t>(num_threads));
423}
424
425// =============================================================================
426// Edge Case Tests
427// =============================================================================
428
429TEST_F(BackendRegistryTest, EmptyNameRegistration)
430{
432 "", &test_backend::create);
433 // Empty name should still be valid (implementation-dependent)
434 // but we test that it doesn't crash
435 if (result.is_ok()) {
436 EXPECT_TRUE(backend_registry::instance().has_backend(""));
437 }
438}
439
440TEST_F(BackendRegistryTest, RegisterUnregisterRegister)
441{
442 backend_registry::instance().register_backend("test", &test_backend::create);
445 "test", &test_backend_alt::create);
446 EXPECT_TRUE(result.is_ok());
447
448 auto backend = backend_registry::instance().create("test");
449 ASSERT_NE(backend, nullptr);
450 // After re-registration with alt type, should return postgres type
451 EXPECT_EQ(backend->type(), database_types::postgres);
452}
453
454TEST_F(BackendRegistryTest, SingletonConsistency)
455{
456 auto& instance1 = backend_registry::instance();
457 auto& instance2 = backend_registry::instance();
458 EXPECT_EQ(&instance1, &instance2);
459}
460
461int main(int argc, char** argv)
462{
463 ::testing::InitGoogleTest(&argc, argv);
464 return RUN_ALL_TESTS();
465}
CRTP template base class for database backends.
Registry for database backend plugins.
TEST_F(BackendRegistryTest, RegisterBackendSucceeds)
CRTP template base class for database backends.
std::vector< std::string > available_backends() const
Get list of all registered backend names.
void clear()
Clear all registered backends (for testing)
kcenon::common::VoidResult unregister_backend(const std::string &name)
Unregister a backend (for testing or dynamic unloading)
static backend_registry & instance()
Get the singleton instance.
std::unique_ptr< database_backend > create(const std::string &name) const
Create a backend instance by name.
kcenon::common::VoidResult register_backend(const std::string &name, backend_factory_fn factory)
Register a backend factory function.
size_t backend_count() const
Get number of registered backends.
Abstract interface for database backends.
std::unique_ptr< database_backend > create_backend(const std::string &name)
Convenience function for creating backends (static method style)
std::vector< database_row > database_result
bool has_backend(const std::string &name)
Convenience function for checking backend availability.
std::vector< std::string > available_backends()
Convenience function for getting available backends.
database_types
Represents various database backends or modes.
@ sqlite
Indicates a SQLite database.
@ postgres
Indicates a PostgreSQL database.