Line data Source code
1 : #define BOOST_TEST_DYN_LINK
2 : #define BOOST_TEST_MODULE (ReverseLookup test)
3 :
4 : #include <boost/test/unit_test.hpp>
5 :
6 : #include <dirent.h>
7 : #include <algorithm>
8 : #include <chrono>
9 : #include <fstream>
10 : #include <iostream>
11 : #include <iterator>
12 : #include <map>
13 : #include <memory>
14 : #include <regex>
15 : #include <set>
16 : #include <sstream>
17 : #include <string>
18 : #include <vector>
19 :
20 : #include "../DatabaseConfigurationInterface.h"
21 :
22 1 : std::string readFileContent(const std::string& filepath) {
23 1 : std::ifstream file(filepath);
24 1 : if (!file.is_open()) {
25 0 : return "";
26 : }
27 1 : std::stringstream buffer;
28 1 : buffer << file.rdbuf();
29 1 : return buffer.str();
30 1 : }
31 :
32 1 : std::vector<std::string> listJsonFiles(const std::string& dirPath) {
33 1 : std::vector<std::string> files;
34 :
35 1 : DIR* dir = opendir(dirPath.c_str());
36 1 : if (dir == nullptr) {
37 0 : return files;
38 : }
39 :
40 : struct dirent* entry;
41 11 : while ((entry = readdir(dir)) != nullptr) {
42 10 : std::string name = entry->d_name;
43 10 : if (name.size() > 5 && name.substr(name.size() - 5) == ".json" && name != "index.json") {
44 7 : files.push_back(dirPath + "/" + name);
45 : }
46 10 : }
47 1 : closedir(dir);
48 1 : return files;
49 0 : }
50 :
51 4 : bool isFileSystemBackend() {
52 4 : auto uri = std::getenv("ARTDAQ_DATABASE_URI");
53 4 : if (uri) {
54 4 : std::string uriStr(uri);
55 4 : return uriStr.find("filesystemdb") != std::string::npos;
56 4 : }
57 0 : return false;
58 : }
59 :
60 2 : std::string getDatabasePath() {
61 2 : auto uri = std::getenv("ARTDAQ_DATABASE_URI");
62 2 : if (uri) {
63 2 : std::string uriStr(uri);
64 2 : if (uriStr.find("filesystemdb:///") == 0) {
65 1 : return uriStr.substr(16);
66 1 : } else if (uriStr.find("filesystemdb://") == 0) {
67 0 : return uriStr.substr(15);
68 : }
69 1 : return uriStr;
70 2 : }
71 0 : return "";
72 : }
73 :
74 0 : std::string extractCompositionName(const std::string& jsonContent) {
75 0 : std::regex pattern("\"name\"\\s*:\\s*\"([^\"]+)\"");
76 0 : std::smatch match;
77 :
78 0 : if (std::regex_search(jsonContent, match, pattern)) {
79 0 : return match[1].str();
80 : }
81 0 : return "";
82 0 : }
83 :
84 0 : bool compositionContains(const std::string& jsonContent, const std::string& configType, const std::string& version) {
85 0 : bool hasConfigType = jsonContent.find("\"" + configType + "\"") != std::string::npos;
86 :
87 0 : std::regex versionPattern("\"version\"\\s*:\\s*\"" + version + "\"");
88 0 : bool hasVersion = std::regex_search(jsonContent, versionPattern);
89 :
90 0 : return hasConfigType && hasVersion;
91 0 : }
92 :
93 0 : int countCompositionsContaining(const std::string& globalConfigDir, const std::string& configType, const std::string& version) {
94 0 : auto files = listJsonFiles(globalConfigDir);
95 0 : int count = 0;
96 :
97 0 : for (const auto& filepath : files) {
98 0 : std::string content = readFileContent(filepath);
99 0 : if (content.empty()) continue;
100 :
101 0 : if (compositionContains(content, configType, version)) {
102 0 : count++;
103 : }
104 0 : }
105 0 : return count;
106 0 : }
107 :
108 1 : std::set<std::string> extractOidsFromIndex(const std::string& indexPath) {
109 1 : std::set<std::string> oids;
110 1 : std::string content = readFileContent(indexPath);
111 1 : if (content.empty()) {
112 0 : return oids;
113 : }
114 :
115 1 : std::regex pattern("\"oid\"\\s*:\\s*\"([a-f0-9]+)\"");
116 1 : auto begin = std::sregex_iterator(content.begin(), content.end(), pattern);
117 1 : auto end = std::sregex_iterator();
118 :
119 1 : for (auto it = begin; it != end; ++it) {
120 0 : oids.insert((*it)[1].str());
121 1 : }
122 1 : return oids;
123 1 : }
124 :
125 7 : std::string getOidFromFilename(const std::string& filepath) {
126 7 : size_t lastSlash = filepath.rfind('/');
127 7 : std::string filename = (lastSlash != std::string::npos) ? filepath.substr(lastSlash + 1) : filepath;
128 :
129 7 : if (filename.size() > 5 && filename.substr(filename.size() - 5) == ".json") {
130 7 : return filename.substr(0, filename.size() - 5);
131 : }
132 0 : return "";
133 7 : }
134 :
135 : using namespace ots;
136 :
137 : struct ReverseLookupTestData {
138 2 : ReverseLookupTestData() {
139 2 : srand(time(nullptr));
140 2 : baseVersion = rand() % 99999 + 100000;
141 2 : dbPath = getDatabasePath();
142 2 : std::cout << "=== REVERSE LOOKUP TEST SUITE ===\n";
143 2 : std::cout << "Base version: " << baseVersion << "\n";
144 2 : std::cout << "Database path: " << dbPath << "\n\n";
145 2 : }
146 :
147 2 : ~ReverseLookupTestData() { std::cout << "\n=== TEST SUITE COMPLETE ===\n"; }
148 :
149 : int baseVersion;
150 : std::string dbPath;
151 : };
152 :
153 : ReverseLookupTestData fixture;
154 :
155 : BOOST_AUTO_TEST_SUITE(reverse_lookup_test)
156 :
157 4 : BOOST_AUTO_TEST_CASE(basic_reverse_lookup) {
158 2 : std::cout << "\n=== TEST 1: Basic reverse lookup ===\n";
159 :
160 2 : auto ifc = DatabaseConfigurationInterface();
161 :
162 2 : int version1 = fixture.baseVersion;
163 2 : int version2 = fixture.baseVersion + 1;
164 :
165 2 : std::shared_ptr<ConfigurationBase> cfg1 = std::make_shared<TestConfiguration001>();
166 2 : cfg1->getView().version = version1;
167 2 : auto result1 = ifc.saveActiveVersion(cfg1.get());
168 2 : BOOST_CHECK_EQUAL(result1, 0);
169 :
170 2 : std::shared_ptr<ConfigurationBase> cfg2 = std::make_shared<TestConfiguration002>();
171 2 : cfg2->getView().version = version2;
172 2 : auto result2 = ifc.saveActiveVersion(cfg2.get());
173 2 : BOOST_CHECK_EQUAL(result2, 0);
174 :
175 2 : std::cout << "Created versions: " << version1 << ", " << version2 << "\n";
176 :
177 2 : auto compositionMap = DatabaseConfigurationInterface::config_version_map_t{};
178 2 : compositionMap[cfg1->getConfigurationName()] = version1;
179 2 : compositionMap[cfg2->getConfigurationName()] = version2;
180 :
181 6 : std::string compositionName = std::string{"TestComp_"} + std::to_string(fixture.baseVersion);
182 :
183 2 : ifc.storeGlobalConfiguration(compositionMap, compositionName);
184 2 : std::cout << "Created composition: " << compositionName << "\n";
185 :
186 2 : std::cout << "Performing reverse lookup for " << cfg1->getConfigurationName() << " v" << version1 << "\n";
187 :
188 2 : auto foundCompositions = ifc.findCompositionsContaining(cfg1->getConfigurationName(), std::to_string(version1));
189 :
190 2 : std::cout << "Found " << foundCompositions.size() << " composition(s)\n";
191 4 : for (auto const& comp : foundCompositions) {
192 2 : std::cout << " - " << comp << "\n";
193 : }
194 :
195 2 : bool found = (foundCompositions.find(compositionName) != foundCompositions.end());
196 :
197 2 : BOOST_CHECK_EQUAL(found, true);
198 2 : std::cout << " Verified: Composition found via reverse lookup\n";
199 2 : }
200 :
201 4 : BOOST_AUTO_TEST_CASE(multiple_compositions_same_version) {
202 2 : std::cout << "\n=== TEST 2: Multiple compositions same version ===\n";
203 :
204 2 : auto ifc = DatabaseConfigurationInterface();
205 :
206 2 : int sharedVersion = fixture.baseVersion + 10;
207 :
208 2 : std::shared_ptr<ConfigurationBase> cfgShared = std::make_shared<TestConfiguration001>();
209 2 : cfgShared->getView().version = sharedVersion;
210 2 : ifc.saveActiveVersion(cfgShared.get());
211 :
212 2 : std::cout << "Created shared version: " << sharedVersion << "\n";
213 :
214 2 : auto map1 = DatabaseConfigurationInterface::config_version_map_t{};
215 2 : map1[cfgShared->getConfigurationName()] = sharedVersion;
216 :
217 6 : std::string comp1Name = std::string{"Comp1_"} + std::to_string(fixture.baseVersion);
218 2 : ifc.storeGlobalConfiguration(map1, comp1Name);
219 2 : std::cout << "Created composition 1: " << comp1Name << "\n";
220 :
221 2 : auto map2 = DatabaseConfigurationInterface::config_version_map_t{};
222 2 : map2[cfgShared->getConfigurationName()] = sharedVersion;
223 :
224 6 : std::string comp2Name = std::string{"Comp2_"} + std::to_string(fixture.baseVersion);
225 2 : ifc.storeGlobalConfiguration(map2, comp2Name);
226 2 : std::cout << "Created composition 2: " << comp2Name << "\n";
227 :
228 2 : auto foundCompositions = ifc.findCompositionsContaining(cfgShared->getConfigurationName(), std::to_string(sharedVersion));
229 :
230 2 : std::cout << "Reverse lookup found " << foundCompositions.size() << " compositions\n";
231 :
232 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), 2);
233 :
234 2 : bool found1 = (foundCompositions.find(comp1Name) != foundCompositions.end());
235 2 : bool found2 = (foundCompositions.find(comp2Name) != foundCompositions.end());
236 :
237 2 : BOOST_CHECK_EQUAL(found1, true);
238 2 : BOOST_CHECK_EQUAL(found2, true);
239 :
240 2 : std::cout << " Verified: Both compositions found\n";
241 2 : }
242 :
243 4 : BOOST_AUTO_TEST_CASE(reverse_lookup_no_matches) {
244 2 : std::cout << "\n=== TEST 3: Reverse lookup no matches ===\n";
245 :
246 2 : auto ifc = DatabaseConfigurationInterface();
247 :
248 2 : int orphanVersion = fixture.baseVersion + 100;
249 :
250 2 : std::shared_ptr<ConfigurationBase> cfgOrphan = std::make_shared<TestConfiguration001>();
251 2 : cfgOrphan->getView().version = orphanVersion;
252 2 : ifc.saveActiveVersion(cfgOrphan.get());
253 :
254 2 : std::cout << "Created orphan version (not in any composition): " << orphanVersion << "\n";
255 :
256 2 : auto foundCompositions = ifc.findCompositionsContaining(cfgOrphan->getConfigurationName(), std::to_string(orphanVersion));
257 :
258 2 : std::cout << "Reverse lookup found " << foundCompositions.size() << " compositions\n";
259 :
260 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), 0);
261 2 : std::cout << " Verified: No compositions found (as expected)\n";
262 2 : }
263 :
264 4 : BOOST_AUTO_TEST_CASE(version_multiple_positions_same_composition) {
265 2 : std::cout << "\n=== TEST 4: Version multiple positions same composition ===\n";
266 :
267 2 : auto ifc = DatabaseConfigurationInterface();
268 :
269 2 : int version1 = fixture.baseVersion + 200;
270 2 : int version2 = fixture.baseVersion + 201;
271 :
272 2 : std::shared_ptr<ConfigurationBase> cfg1a = std::make_shared<TestConfiguration001>();
273 2 : cfg1a->getView().version = version1;
274 2 : ifc.saveActiveVersion(cfg1a.get());
275 :
276 2 : std::shared_ptr<ConfigurationBase> cfg1b = std::make_shared<TestConfiguration001>();
277 2 : cfg1b->getView().version = version2;
278 2 : ifc.saveActiveVersion(cfg1b.get());
279 :
280 2 : auto compositionMap = DatabaseConfigurationInterface::config_version_map_t{};
281 2 : compositionMap[cfg1a->getConfigurationName()] = version1;
282 :
283 6 : std::string compName = std::string{"SingleTypeComp_"} + std::to_string(fixture.baseVersion);
284 :
285 2 : ifc.storeGlobalConfiguration(compositionMap, compName);
286 :
287 2 : auto found1 = ifc.findCompositionsContaining(cfg1a->getConfigurationName(), std::to_string(version1));
288 :
289 2 : BOOST_CHECK_EQUAL(found1.size(), 1);
290 :
291 2 : auto found2 = ifc.findCompositionsContaining(cfg1b->getConfigurationName(), std::to_string(version2));
292 :
293 2 : BOOST_CHECK_EQUAL(found2.size(), 0);
294 :
295 2 : std::cout << " Verified: Only the stored version is found\n";
296 2 : }
297 :
298 4 : BOOST_AUTO_TEST_CASE(performance_many_compositions) {
299 2 : std::cout << "\n=== TEST 5: Performance many compositions ===\n";
300 :
301 2 : auto ifc = DatabaseConfigurationInterface();
302 :
303 2 : int targetVersion = fixture.baseVersion + 500;
304 :
305 2 : std::shared_ptr<ConfigurationBase> cfgTarget = std::make_shared<TestConfiguration001>();
306 2 : cfgTarget->getView().version = targetVersion;
307 2 : ifc.saveActiveVersion(cfgTarget.get());
308 :
309 2 : std::string targetCompName;
310 :
311 22 : for (int i = 0; i < 10; ++i) {
312 20 : int otherVersion = fixture.baseVersion + 600 + i;
313 :
314 20 : std::shared_ptr<ConfigurationBase> cfgOther = std::make_shared<TestConfiguration002>();
315 20 : cfgOther->getView().version = otherVersion;
316 20 : ifc.saveActiveVersion(cfgOther.get());
317 :
318 20 : auto map = DatabaseConfigurationInterface::config_version_map_t{};
319 :
320 20 : if (i == 5) {
321 2 : map[cfgTarget->getConfigurationName()] = targetVersion;
322 2 : map[cfgOther->getConfigurationName()] = otherVersion;
323 6 : targetCompName = std::string{"PerfComp_"} + std::to_string(i) + "_" + std::to_string(fixture.baseVersion);
324 : } else {
325 18 : map[cfgOther->getConfigurationName()] = otherVersion;
326 : }
327 :
328 60 : std::string compName = std::string{"PerfComp_"} + std::to_string(i) + "_" + std::to_string(fixture.baseVersion);
329 20 : ifc.storeGlobalConfiguration(map, compName);
330 20 : }
331 :
332 2 : std::cout << "Created 10 compositions, target is in: " << targetCompName << "\n";
333 :
334 2 : auto start = std::chrono::high_resolution_clock::now();
335 :
336 2 : auto foundCompositions = ifc.findCompositionsContaining(cfgTarget->getConfigurationName(), std::to_string(targetVersion));
337 :
338 2 : auto end = std::chrono::high_resolution_clock::now();
339 2 : auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
340 :
341 2 : std::cout << "Reverse lookup completed in " << duration.count() << "ms\n";
342 :
343 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), 1);
344 :
345 2 : bool foundTarget = (foundCompositions.find(targetCompName) != foundCompositions.end());
346 2 : BOOST_CHECK_EQUAL(foundTarget, true);
347 :
348 2 : std::cout << " Verified: Correct composition found efficiently\n";
349 :
350 2 : BOOST_CHECK_LT(duration.count(), 5000);
351 2 : }
352 :
353 4 : BOOST_AUTO_TEST_CASE(raw_storage_verify_composition) {
354 2 : std::cout << "\n=== TEST 6: Raw storage verification ===\n";
355 :
356 2 : if (!isFileSystemBackend()) {
357 1 : std::cout << " Skipping: Not using FileSystemDB backend\n";
358 1 : BOOST_CHECK(true);
359 1 : return;
360 : }
361 :
362 1 : auto ifc = DatabaseConfigurationInterface();
363 :
364 1 : int version1 = fixture.baseVersion + 700;
365 1 : int version2 = fixture.baseVersion + 701;
366 :
367 1 : std::shared_ptr<ConfigurationBase> cfg1 = std::make_shared<TestConfiguration001>();
368 1 : cfg1->getView().version = version1;
369 1 : ifc.saveActiveVersion(cfg1.get());
370 :
371 1 : std::shared_ptr<ConfigurationBase> cfg2 = std::make_shared<TestConfiguration002>();
372 1 : cfg2->getView().version = version2;
373 1 : ifc.saveActiveVersion(cfg2.get());
374 :
375 1 : auto compositionMap = DatabaseConfigurationInterface::config_version_map_t{};
376 1 : compositionMap[cfg1->getConfigurationName()] = version1;
377 1 : compositionMap[cfg2->getConfigurationName()] = version2;
378 :
379 3 : std::string compName = std::string{"RawStorageComp_"} + std::to_string(fixture.baseVersion);
380 1 : ifc.storeGlobalConfiguration(compositionMap, compName);
381 :
382 1 : std::cout << "Created composition: " << compName << "\n";
383 :
384 1 : auto apiResults = ifc.findCompositionsContaining(cfg1->getConfigurationName(), std::to_string(version1));
385 :
386 1 : std::cout << " API found " << apiResults.size() << " composition(s) containing v" << version1 << "\n";
387 :
388 1 : BOOST_CHECK_GE(apiResults.size(), static_cast<size_t>(1));
389 1 : bool foundOurs = (apiResults.find(compName) != apiResults.end());
390 1 : BOOST_CHECK_EQUAL(foundOurs, true);
391 :
392 1 : std::cout << " Verified: Composition accessible via API\n";
393 1 : }
394 :
395 4 : BOOST_AUTO_TEST_CASE(index_consistency_validation) {
396 2 : std::cout << "\n=== TEST 7: Index consistency validation ===\n";
397 :
398 2 : if (!isFileSystemBackend()) {
399 1 : std::cout << " Skipping: Not using FileSystemDB backend\n";
400 1 : BOOST_CHECK(true);
401 1 : return;
402 : }
403 :
404 1 : std::string collectionDir = fixture.dbPath + "/TestConfiguration001";
405 1 : std::string indexPath = collectionDir + "/index.json";
406 :
407 1 : auto indexOids = extractOidsFromIndex(indexPath);
408 1 : std::cout << " OIDs in index: " << indexOids.size() << "\n";
409 :
410 1 : auto jsonFiles = listJsonFiles(collectionDir);
411 1 : std::set<std::string> fileOids;
412 8 : for (const auto& filepath : jsonFiles) {
413 7 : std::string oid = getOidFromFilename(filepath);
414 7 : if (!oid.empty() && oid != "index") {
415 7 : fileOids.insert(oid);
416 : }
417 7 : }
418 1 : std::cout << " Files on disk: " << fileOids.size() << "\n";
419 :
420 1 : int orphaned = 0;
421 8 : for (const auto& fileOid : fileOids) {
422 7 : if (indexOids.find(fileOid) == indexOids.end()) {
423 7 : std::cout << " WARNING: Orphaned file: " << fileOid << ".json\n";
424 7 : orphaned++;
425 : }
426 : }
427 :
428 1 : int missing = 0;
429 1 : for (const auto& indexOid : indexOids) {
430 0 : if (fileOids.find(indexOid) == fileOids.end()) {
431 0 : std::cout << " WARNING: Missing file: " << indexOid << ".json\n";
432 0 : missing++;
433 : }
434 : }
435 :
436 1 : std::cout << " Orphaned files: " << orphaned << "\n";
437 1 : std::cout << " Missing files: " << missing << "\n";
438 :
439 1 : BOOST_WARN_EQUAL(orphaned, 0);
440 1 : BOOST_CHECK_EQUAL(missing, 0);
441 :
442 1 : std::cout << " Verified: Index consistency checked\n";
443 1 : }
444 :
445 4 : BOOST_AUTO_TEST_CASE(stress_test_many_compositions) {
446 2 : std::cout << "\n=== TEST 8: Stress test - many compositions ===\n";
447 :
448 2 : auto ifc = DatabaseConfigurationInterface();
449 :
450 2 : const int NUM_COMPOSITIONS = 20;
451 2 : int baseVer = fixture.baseVersion + 800;
452 :
453 2 : std::shared_ptr<ConfigurationBase> cfgShared = std::make_shared<TestConfiguration001>();
454 2 : cfgShared->getView().version = baseVer;
455 2 : ifc.saveActiveVersion(cfgShared.get());
456 :
457 2 : auto startCreate = std::chrono::high_resolution_clock::now();
458 :
459 2 : int compositionsWithShared = 0;
460 42 : for (int i = 0; i < NUM_COMPOSITIONS; i++) {
461 40 : int otherVersion = baseVer + 100 + i;
462 :
463 40 : std::shared_ptr<ConfigurationBase> cfgOther = std::make_shared<TestConfiguration002>();
464 40 : cfgOther->getView().version = otherVersion;
465 40 : ifc.saveActiveVersion(cfgOther.get());
466 :
467 40 : auto map = DatabaseConfigurationInterface::config_version_map_t{};
468 :
469 40 : if (i % 2 == 0) {
470 20 : map[cfgShared->getConfigurationName()] = baseVer;
471 20 : compositionsWithShared++;
472 : }
473 40 : map[cfgOther->getConfigurationName()] = otherVersion;
474 :
475 120 : std::string compName = std::string{"StressComp_"} + std::to_string(i) + "_" + std::to_string(fixture.baseVersion);
476 40 : ifc.storeGlobalConfiguration(map, compName);
477 40 : }
478 :
479 2 : auto endCreate = std::chrono::high_resolution_clock::now();
480 2 : auto createDuration = std::chrono::duration_cast<std::chrono::milliseconds>(endCreate - startCreate);
481 :
482 2 : std::cout << " Created " << NUM_COMPOSITIONS << " compositions in " << createDuration.count() << "ms\n";
483 2 : std::cout << " " << compositionsWithShared << " compositions contain shared version\n";
484 :
485 2 : auto startLookup = std::chrono::high_resolution_clock::now();
486 :
487 2 : auto foundCompositions = ifc.findCompositionsContaining(cfgShared->getConfigurationName(), std::to_string(baseVer));
488 :
489 2 : auto endLookup = std::chrono::high_resolution_clock::now();
490 2 : auto lookupDuration = std::chrono::duration_cast<std::chrono::milliseconds>(endLookup - startLookup);
491 :
492 2 : std::cout << " Reverse lookup completed in " << lookupDuration.count() << "ms\n";
493 2 : std::cout << " Found " << foundCompositions.size() << " compositions\n";
494 :
495 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), static_cast<size_t>(compositionsWithShared));
496 :
497 2 : BOOST_CHECK_LT(lookupDuration.count(), 10000);
498 :
499 2 : std::cout << " Verified: Stress test passed\n";
500 2 : }
501 :
502 4 : BOOST_AUTO_TEST_CASE(edge_case_config_type_not_in_compositions) {
503 2 : std::cout << "\n=== TEST 9: Edge case - config type not in compositions ===\n";
504 :
505 2 : auto ifc = DatabaseConfigurationInterface();
506 :
507 2 : int testVersion = fixture.baseVersion + 900;
508 :
509 2 : std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
510 2 : cfg->getView().version = testVersion;
511 2 : ifc.saveActiveVersion(cfg.get());
512 :
513 2 : std::cout << "Created standalone version " << testVersion << " (not in any composition)\n";
514 :
515 2 : auto foundCompositions = ifc.findCompositionsContaining(cfg->getConfigurationName(), std::to_string(testVersion));
516 :
517 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), 0);
518 2 : std::cout << " Verified: Empty result for version not in any composition\n";
519 2 : }
520 :
521 4 : BOOST_AUTO_TEST_CASE(edge_case_nonexistent_version) {
522 2 : std::cout << "\n=== TEST 10: Edge case - nonexistent version ===\n";
523 :
524 2 : auto ifc = DatabaseConfigurationInterface();
525 :
526 2 : int nonexistentVersion = 999888777;
527 :
528 6 : auto foundCompositions = ifc.findCompositionsContaining("TestConfiguration001", std::to_string(nonexistentVersion));
529 :
530 2 : std::cout << " Searched for nonexistent version " << nonexistentVersion << "\n";
531 2 : std::cout << " Found " << foundCompositions.size() << " compositions\n";
532 :
533 2 : BOOST_CHECK_EQUAL(foundCompositions.size(), 0);
534 2 : std::cout << " Verified: Empty result for nonexistent version\n";
535 2 : }
536 :
537 4 : BOOST_AUTO_TEST_CASE(reverse_lookup_different_config_types) {
538 2 : std::cout << "\n=== TEST 11: Reverse lookup different config types ===\n";
539 :
540 2 : auto ifc = DatabaseConfigurationInterface();
541 :
542 2 : int version1 = fixture.baseVersion + 1100;
543 2 : int version2 = fixture.baseVersion + 1101;
544 :
545 2 : std::shared_ptr<ConfigurationBase> cfg1 = std::make_shared<TestConfiguration001>();
546 2 : cfg1->getView().version = version1;
547 2 : ifc.saveActiveVersion(cfg1.get());
548 :
549 2 : std::shared_ptr<ConfigurationBase> cfg2 = std::make_shared<TestConfiguration002>();
550 2 : cfg2->getView().version = version2;
551 2 : ifc.saveActiveVersion(cfg2.get());
552 :
553 2 : auto compositionMap = DatabaseConfigurationInterface::config_version_map_t{};
554 2 : compositionMap[cfg1->getConfigurationName()] = version1;
555 2 : compositionMap[cfg2->getConfigurationName()] = version2;
556 :
557 6 : std::string compName = std::string{"MultiTypeComp_"} + std::to_string(fixture.baseVersion);
558 2 : ifc.storeGlobalConfiguration(compositionMap, compName);
559 :
560 2 : std::cout << "Created composition with both config types\n";
561 :
562 2 : auto found1 = ifc.findCompositionsContaining(cfg1->getConfigurationName(), std::to_string(version1));
563 2 : BOOST_CHECK_EQUAL(found1.size(), 1);
564 2 : BOOST_CHECK(found1.find(compName) != found1.end());
565 :
566 2 : auto found2 = ifc.findCompositionsContaining(cfg2->getConfigurationName(), std::to_string(version2));
567 2 : BOOST_CHECK_EQUAL(found2.size(), 1);
568 2 : BOOST_CHECK(found2.find(compName) != found2.end());
569 :
570 2 : std::cout << " Verified: Both config types correctly find the composition\n";
571 2 : }
572 :
573 4 : BOOST_AUTO_TEST_CASE(performance_multiple_lookups) {
574 2 : std::cout << "\n=== TEST 12: Performance - multiple reverse lookups ===\n";
575 :
576 2 : auto ifc = DatabaseConfigurationInterface();
577 :
578 2 : const int NUM_LOOKUPS = 20;
579 2 : int baseVer = fixture.baseVersion + 1200;
580 :
581 2 : std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
582 2 : cfg->getView().version = baseVer;
583 2 : ifc.saveActiveVersion(cfg.get());
584 :
585 2 : auto map = DatabaseConfigurationInterface::config_version_map_t{};
586 2 : map[cfg->getConfigurationName()] = baseVer;
587 :
588 6 : std::string compName = std::string{"PerfLookupComp_"} + std::to_string(fixture.baseVersion);
589 2 : ifc.storeGlobalConfiguration(map, compName);
590 :
591 2 : auto startTime = std::chrono::high_resolution_clock::now();
592 :
593 42 : for (int i = 0; i < NUM_LOOKUPS; i++) {
594 40 : auto found = ifc.findCompositionsContaining(cfg->getConfigurationName(), std::to_string(baseVer));
595 40 : BOOST_CHECK_EQUAL(found.size(), 1);
596 40 : }
597 :
598 2 : auto endTime = std::chrono::high_resolution_clock::now();
599 2 : auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
600 :
601 2 : std::cout << " Completed " << NUM_LOOKUPS << " reverse lookups in " << duration.count() << "ms\n";
602 2 : std::cout << " Average: " << (duration.count() / NUM_LOOKUPS) << "ms per lookup\n";
603 :
604 2 : BOOST_CHECK_LT(duration.count(), 10000);
605 :
606 2 : std::cout << " Verified: Multiple lookups performance acceptable\n";
607 2 : }
608 :
609 4 : BOOST_AUTO_TEST_CASE(api_consistency_multiple_compositions) {
610 2 : std::cout << "\n=== TEST 13: API consistency - multiple compositions ===\n";
611 :
612 2 : auto ifc = DatabaseConfigurationInterface();
613 :
614 2 : int sharedVersion = fixture.baseVersion + 1300;
615 2 : const int NUM_COMPOSITIONS = 3;
616 :
617 2 : std::shared_ptr<ConfigurationBase> cfgShared = std::make_shared<TestConfiguration001>();
618 2 : cfgShared->getView().version = sharedVersion;
619 2 : ifc.saveActiveVersion(cfgShared.get());
620 :
621 2 : std::vector<std::string> createdComps;
622 8 : for (int i = 0; i < NUM_COMPOSITIONS; i++) {
623 6 : int otherVersion = sharedVersion + 100 + i;
624 :
625 6 : std::shared_ptr<ConfigurationBase> cfgOther = std::make_shared<TestConfiguration002>();
626 6 : cfgOther->getView().version = otherVersion;
627 6 : ifc.saveActiveVersion(cfgOther.get());
628 :
629 6 : auto map = DatabaseConfigurationInterface::config_version_map_t{};
630 6 : map[cfgShared->getConfigurationName()] = sharedVersion;
631 6 : map[cfgOther->getConfigurationName()] = otherVersion;
632 :
633 18 : std::string compName = std::string{"ConsistencyComp_"} + std::to_string(i) + "_" + std::to_string(fixture.baseVersion);
634 6 : ifc.storeGlobalConfiguration(map, compName);
635 6 : createdComps.push_back(compName);
636 6 : }
637 :
638 2 : std::cout << " Created " << NUM_COMPOSITIONS << " compositions with shared version\n";
639 :
640 2 : auto apiResults = ifc.findCompositionsContaining(cfgShared->getConfigurationName(), std::to_string(sharedVersion));
641 2 : std::cout << " API found " << apiResults.size() << " compositions\n";
642 :
643 2 : BOOST_CHECK_GE(apiResults.size(), static_cast<size_t>(NUM_COMPOSITIONS));
644 :
645 2 : int foundCount = 0;
646 8 : for (const auto& comp : createdComps) {
647 6 : bool found = apiResults.find(comp) != apiResults.end();
648 6 : if (found) foundCount++;
649 6 : BOOST_CHECK_MESSAGE(found, "Composition " << comp << " should be in API results");
650 : }
651 :
652 2 : std::cout << " Found " << foundCount << " of " << NUM_COMPOSITIONS << " expected compositions\n";
653 2 : std::cout << " Verified: API returns all expected compositions\n";
654 2 : }
655 :
656 4 : BOOST_AUTO_TEST_CASE(comprehensive_reverse_lookup_verification) {
657 2 : std::cout << "\n=== TEST 14: Comprehensive reverse lookup verification ===\n";
658 :
659 2 : auto ifc = DatabaseConfigurationInterface();
660 :
661 2 : int baseVer = fixture.baseVersion + 1400;
662 2 : const int NUM_CONFIGS = 3;
663 2 : const int NUM_COMPOSITIONS = 4;
664 :
665 2 : std::vector<int> createdVersions;
666 8 : for (int i = 0; i < NUM_CONFIGS; i++) {
667 6 : int version = baseVer + i;
668 6 : std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
669 6 : cfg->getView().version = version;
670 6 : ifc.saveActiveVersion(cfg.get());
671 6 : createdVersions.push_back(version);
672 6 : }
673 :
674 2 : std::map<std::string, std::vector<int>> compVersions;
675 :
676 10 : for (int i = 0; i < NUM_COMPOSITIONS; i++) {
677 8 : auto map = DatabaseConfigurationInterface::config_version_map_t{};
678 :
679 8 : int v1 = createdVersions[i % NUM_CONFIGS];
680 16 : map["TestConfiguration001"] = v1;
681 :
682 8 : int v2 = baseVer + 100 + i;
683 8 : std::shared_ptr<ConfigurationBase> cfg2 = std::make_shared<TestConfiguration002>();
684 8 : cfg2->getView().version = v2;
685 8 : ifc.saveActiveVersion(cfg2.get());
686 16 : map["TestConfiguration002"] = v2;
687 :
688 24 : std::string compName = std::string{"ComprehensiveComp_"} + std::to_string(i) + "_" + std::to_string(fixture.baseVersion);
689 8 : ifc.storeGlobalConfiguration(map, compName);
690 8 : compVersions[compName] = {v1, v2};
691 8 : }
692 :
693 2 : std::cout << " Created " << NUM_COMPOSITIONS << " compositions for comprehensive scan\n";
694 :
695 2 : int successfulLookups = 0;
696 2 : int failedLookups = 0;
697 :
698 10 : for (const auto& [compName, versions] : compVersions) {
699 24 : auto results = ifc.findCompositionsContaining("TestConfiguration001", std::to_string(versions[0]));
700 8 : bool found = results.find(compName) != results.end();
701 8 : if (found) {
702 8 : successfulLookups++;
703 : } else {
704 0 : failedLookups++;
705 0 : std::cout << " WARNING: Composition " << compName << " not found via v" << versions[0] << "\n";
706 : }
707 8 : }
708 :
709 2 : std::cout << " Successful lookups: " << successfulLookups << "\n";
710 2 : std::cout << " Failed lookups: " << failedLookups << "\n";
711 :
712 2 : BOOST_CHECK_EQUAL(failedLookups, 0);
713 2 : std::cout << " Verified: Comprehensive scan complete\n";
714 2 : }
715 :
716 : BOOST_AUTO_TEST_SUITE_END()
|