LCOV - code coverage report
Current view: top level - /opt/artdaq/srcs/artdaq-database/test/CaseStudy/OtsDaqCPPInterface/test010 - ConcurrentStore_t.cc (source / functions) Coverage Total Hit
Test: artdaq.info.cleaned Lines: 94.5 % 364 344
Test Date: 2026-10-29 00:35:58 Functions: 100.0 % 30 30

            Line data    Source code
       1              : #define BOOST_TEST_DYN_LINK
       2              : #define BOOST_TEST_MODULE (ConcurrentStore test)
       3              : 
       4              : #include <boost/test/unit_test.hpp>
       5              : 
       6              : #include <algorithm>
       7              : #include <atomic>
       8              : #include <chrono>
       9              : #include <cstdlib>
      10              : #include <iostream>
      11              : #include <memory>
      12              : #include <mutex>
      13              : #include <set>
      14              : #include <string>
      15              : #include <thread>
      16              : #include <vector>
      17              : 
      18              : #include "../DatabaseConfigurationInterface.h"
      19              : 
      20              : using namespace ots;
      21              : 
      22           12 : bool isFileSystemDB() {
      23           12 :   const char* uri = std::getenv("ARTDAQ_DATABASE_URI");
      24           36 :   return uri != nullptr && std::string(uri).find("filesystem") != std::string::npos;
      25              : }
      26              : 
      27              : struct ConcurrentStoreTestData {
      28            2 :   ConcurrentStoreTestData() {
      29            2 :     srand(time(nullptr));
      30            2 :     baseVersion = rand() % 99999 + 100000;
      31            2 :     std::cout << "=== CONCURRENT STORE TEST SUITE ===\n";
      32            2 :     std::cout << "Base version: " << baseVersion << "\n";
      33            2 :   }
      34              : 
      35            2 :   ~ConcurrentStoreTestData() { std::cout << "\n=== TEST SUITE COMPLETE ===\n"; }
      36              : 
      37           17 :   int createConfig001(DatabaseConfigurationInterface& ifc, int version) {
      38           17 :     std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
      39           17 :     cfg->getView().version = version;
      40           34 :     return ifc.saveActiveVersion(cfg.get());
      41           17 :   }
      42              : 
      43            9 :   int createConfig002(DatabaseConfigurationInterface& ifc, int version) {
      44            9 :     std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration002>();
      45            9 :     cfg->getView().version = version;
      46           18 :     return ifc.saveActiveVersion(cfg.get());
      47            9 :   }
      48              : 
      49           40 :   std::string config001Name() {
      50           40 :     std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
      51           80 :     return cfg->getConfigurationName();
      52           40 :   }
      53              : 
      54            7 :   std::string config002Name() {
      55            7 :     std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration002>();
      56           14 :     return cfg->getConfigurationName();
      57            7 :   }
      58              : 
      59              :   int baseVersion;
      60              : };
      61              : 
      62              : ConcurrentStoreTestData fixture;
      63              : 
      64              : BOOST_AUTO_TEST_SUITE(concurrent_store_test)
      65              : 
      66            4 : BOOST_AUTO_TEST_CASE(mt_basic_two_members) {
      67            2 :   std::cout << "\n=== TEST 1: MT store basic two members ===\n";
      68              : 
      69            2 :   if (isFileSystemDB()) {
      70            1 :     std::cout << "  SKIPPED: FileSystemDB has race conditions in configurationComposition\n";
      71            1 :     std::cout << "  See NEED_TO_IMPROVE.md for details\n";
      72            1 :     BOOST_CHECK(true);
      73            1 :     return;
      74              :   }
      75              : 
      76            1 :   auto ifc = DatabaseConfigurationInterface();
      77              : 
      78            1 :   int version1 = fixture.baseVersion;
      79            1 :   int version2 = fixture.baseVersion + 1;
      80              : 
      81            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, version1), 0);
      82            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig002(ifc, version2), 0);
      83            1 :   std::cout << "Step 1: Created versions " << version1 << ", " << version2 << "\n";
      84              : 
      85            1 :   auto map = DatabaseConfigurationInterface::config_version_map_t{};
      86            1 :   map[fixture.config001Name()] = version1;
      87            1 :   map[fixture.config002Name()] = version2;
      88              : 
      89            1 :   std::string compName = "MTBasic_" + std::to_string(fixture.baseVersion);
      90              : 
      91            1 :   std::cout << "Step 2: Calling storeGlobalConfiguration_mt...\n";
      92            1 :   auto start = std::chrono::high_resolution_clock::now();
      93              : 
      94            1 :   BOOST_CHECK_NO_THROW(ifc.storeGlobalConfiguration_mt(map, compName));
      95              : 
      96            1 :   auto end = std::chrono::high_resolution_clock::now();
      97            1 :   auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
      98            1 :   std::cout << "  Completed in " << duration.count() << "ms\n";
      99              : 
     100            1 :   auto loaded = ifc.loadGlobalConfiguration(compName);
     101            1 :   BOOST_CHECK_EQUAL(loaded.size(), 2);
     102            1 :   BOOST_CHECK_EQUAL(loaded[fixture.config001Name()], version1);
     103            1 :   BOOST_CHECK_EQUAL(loaded[fixture.config002Name()], version2);
     104              : 
     105            1 :   std::cout << "  Verified: Composition has 2 members\n";
     106              : 
     107            1 :   auto found1 = ifc.findCompositionsContaining(fixture.config001Name(), std::to_string(version1));
     108            1 :   auto found2 = ifc.findCompositionsContaining(fixture.config002Name(), std::to_string(version2));
     109              : 
     110            1 :   BOOST_CHECK_EQUAL(found1.find(compName) != found1.end(), true);
     111            1 :   BOOST_CHECK_EQUAL(found2.find(compName) != found2.end(), true);
     112              : 
     113            1 :   std::cout << "  Verified: Reverse lookups work\n";
     114            1 : }
     115              : 
     116            4 : BOOST_AUTO_TEST_CASE(mt_many_members) {
     117            2 :   std::cout << "\n=== TEST 2: MT store many members ===\n";
     118              : 
     119            2 :   if (isFileSystemDB()) {
     120            1 :     std::cout << "  SKIPPED: FileSystemDB has race conditions in configurationComposition\n";
     121            1 :     BOOST_CHECK(true);
     122            1 :     return;
     123              :   }
     124              : 
     125            1 :   auto ifc = DatabaseConfigurationInterface();
     126              : 
     127            1 :   const int NUM_VERSIONS = 10;
     128            1 :   std::vector<int> config001Versions;
     129            1 :   std::vector<int> config002Versions;
     130              : 
     131            6 :   for (int i = 0; i < NUM_VERSIONS / 2; ++i) {
     132            5 :     int v1 = fixture.baseVersion + 10 + i;
     133            5 :     int v2 = fixture.baseVersion + 10 + NUM_VERSIONS / 2 + i;
     134              : 
     135            5 :     BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, v1), 0);
     136            5 :     BOOST_REQUIRE_EQUAL(fixture.createConfig002(ifc, v2), 0);
     137              : 
     138            5 :     config001Versions.push_back(v1);
     139            5 :     config002Versions.push_back(v2);
     140              :   }
     141              : 
     142            1 :   std::cout << "Step 1: Created " << NUM_VERSIONS << " configuration versions\n";
     143              : 
     144            1 :   auto map = DatabaseConfigurationInterface::config_version_map_t{};
     145            1 :   map[fixture.config001Name()] = config001Versions[0];
     146            1 :   map[fixture.config002Name()] = config002Versions[0];
     147              : 
     148            1 :   std::string compName = "MTMany_" + std::to_string(fixture.baseVersion);
     149              : 
     150            1 :   std::cout << "Step 2: Calling storeGlobalConfiguration_mt with 2 members...\n";
     151            1 :   auto start = std::chrono::high_resolution_clock::now();
     152              : 
     153            1 :   BOOST_CHECK_NO_THROW(ifc.storeGlobalConfiguration_mt(map, compName));
     154              : 
     155            1 :   auto end = std::chrono::high_resolution_clock::now();
     156            1 :   auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
     157            1 :   std::cout << "  Completed in " << duration.count() << "ms\n";
     158              : 
     159            1 :   auto loaded = ifc.loadGlobalConfiguration(compName);
     160            1 :   BOOST_CHECK_EQUAL(loaded.size(), 2);
     161              : 
     162            1 :   std::cout << "  Verified: Composition created correctly\n";
     163            1 : }
     164              : 
     165            4 : BOOST_AUTO_TEST_CASE(mt_vs_single_threaded_performance) {
     166            2 :   std::cout << "\n=== TEST 3: MT vs single-threaded performance ===\n";
     167              : 
     168            2 :   if (isFileSystemDB()) {
     169            1 :     std::cout << "  SKIPPED: FileSystemDB has race conditions in configurationComposition\n";
     170            1 :     BOOST_CHECK(true);
     171            1 :     return;
     172              :   }
     173              : 
     174            1 :   auto ifc = DatabaseConfigurationInterface();
     175              : 
     176            1 :   int v1_st = fixture.baseVersion + 30;
     177            1 :   int v2_st = fixture.baseVersion + 31;
     178            1 :   int v1_mt = fixture.baseVersion + 32;
     179            1 :   int v2_mt = fixture.baseVersion + 33;
     180              : 
     181            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, v1_st), 0);
     182            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig002(ifc, v2_st), 0);
     183            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, v1_mt), 0);
     184            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig002(ifc, v2_mt), 0);
     185              : 
     186            1 :   auto mapST = DatabaseConfigurationInterface::config_version_map_t{};
     187            1 :   mapST[fixture.config001Name()] = v1_st;
     188            1 :   mapST[fixture.config002Name()] = v2_st;
     189              : 
     190            1 :   std::string compNameST = "PerfST_" + std::to_string(fixture.baseVersion);
     191              : 
     192            1 :   auto startST = std::chrono::high_resolution_clock::now();
     193            1 :   ifc.storeGlobalConfiguration(mapST, compNameST);
     194            1 :   auto endST = std::chrono::high_resolution_clock::now();
     195            1 :   auto durationST = std::chrono::duration_cast<std::chrono::milliseconds>(endST - startST);
     196              : 
     197            1 :   std::cout << "Single-threaded: " << durationST.count() << "ms\n";
     198              : 
     199            1 :   auto mapMT = DatabaseConfigurationInterface::config_version_map_t{};
     200            1 :   mapMT[fixture.config001Name()] = v1_mt;
     201            1 :   mapMT[fixture.config002Name()] = v2_mt;
     202              : 
     203            1 :   std::string compNameMT = "PerfMT_" + std::to_string(fixture.baseVersion);
     204              : 
     205            1 :   auto startMT = std::chrono::high_resolution_clock::now();
     206            1 :   ifc.storeGlobalConfiguration_mt(mapMT, compNameMT);
     207            1 :   auto endMT = std::chrono::high_resolution_clock::now();
     208            1 :   auto durationMT = std::chrono::duration_cast<std::chrono::milliseconds>(endMT - startMT);
     209              : 
     210            1 :   std::cout << "Multi-threaded:  " << durationMT.count() << "ms\n";
     211              : 
     212            1 :   auto loadedST = ifc.loadGlobalConfiguration(compNameST);
     213            1 :   auto loadedMT = ifc.loadGlobalConfiguration(compNameMT);
     214              : 
     215            1 :   BOOST_CHECK_EQUAL(loadedST.size(), 2);
     216            1 :   BOOST_CHECK_EQUAL(loadedMT.size(), 2);
     217              : 
     218            1 :   std::cout << "  Both compositions created correctly\n";
     219            1 : }
     220              : 
     221            4 : BOOST_AUTO_TEST_CASE(concurrent_reads_same_version) {
     222            2 :   std::cout << "\n=== TEST 4: Concurrent reads same version ===\n";
     223              : 
     224            2 :   auto ifc = DatabaseConfigurationInterface();
     225              : 
     226            2 :   int testVersion = fixture.baseVersion + 40;
     227            2 :   std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
     228            2 :   cfg->getView().version = testVersion;
     229            6 :   cfg->getView().fillFromJSON("{\"test\": \"concurrent_read\"}");
     230            2 :   BOOST_REQUIRE_EQUAL(ifc.saveActiveVersion(cfg.get()), 0);
     231              : 
     232            2 :   std::cout << "Setup: Created version " << testVersion << "\n";
     233              : 
     234            2 :   const int NUM_THREADS = 10;
     235            2 :   std::vector<std::thread> threads;
     236            2 :   std::atomic<int> successCount{0};
     237            2 :   std::atomic<int> failCount{0};
     238            2 :   std::mutex outputMutex;
     239              : 
     240            2 :   std::cout << "Launching " << NUM_THREADS << " concurrent read threads...\n";
     241              : 
     242           22 :   for (int i = 0; i < NUM_THREADS; ++i) {
     243           40 :     threads.emplace_back([&ifc, testVersion, &successCount, &failCount, &outputMutex, i]() {
     244              :       try {
     245           20 :         auto localIfc = DatabaseConfigurationInterface();
     246           20 :         std::shared_ptr<ConfigurationBase> readCfg = std::make_shared<TestConfiguration001>();
     247              : 
     248           20 :         auto result = localIfc.fill(readCfg.get(), testVersion);
     249              : 
     250           19 :         if (result == 0) {
     251           19 :           successCount++;
     252              :         } else {
     253            0 :           failCount++;
     254            0 :           std::lock_guard<std::mutex> lock(outputMutex);
     255            0 :           std::cout << "  Thread " << i << " failed to load\n";
     256            0 :         }
     257           20 :       } catch (std::exception const& e) {
     258            0 :         failCount++;
     259            0 :         std::lock_guard<std::mutex> lock(outputMutex);
     260            0 :         std::cout << "  Thread " << i << " exception: " << e.what() << "\n";
     261            0 :       }
     262           20 :     });
     263              :   }
     264              : 
     265           22 :   for (auto& t : threads) {
     266           20 :     t.join();
     267              :   }
     268              : 
     269            2 :   std::cout << "Results: " << successCount << "/" << NUM_THREADS << " successful\n";
     270              : 
     271            4 :   BOOST_CHECK_EQUAL(successCount.load(), NUM_THREADS);
     272            4 :   BOOST_CHECK_EQUAL(failCount.load(), 0);
     273              : 
     274            2 :   std::cout << "  Verified: Concurrent reads succeeded\n";
     275            2 : }
     276              : 
     277            4 : BOOST_AUTO_TEST_CASE(sequential_stores_different_versions) {
     278            2 :   std::cout << "\n=== TEST 5: Sequential stores different versions ===\n";
     279              : 
     280            2 :   auto ifc = DatabaseConfigurationInterface();
     281              : 
     282            2 :   const int NUM_VERSIONS = 5;
     283            2 :   int successCount = 0;
     284            2 :   int failCount = 0;
     285              : 
     286            2 :   std::cout << "Storing " << NUM_VERSIONS << " versions sequentially...\n";
     287              : 
     288           12 :   for (int i = 0; i < NUM_VERSIONS; ++i) {
     289           10 :     int version = fixture.baseVersion + 50 + i;
     290              : 
     291              :     try {
     292           10 :       std::shared_ptr<ConfigurationBase> cfg = std::make_shared<TestConfiguration001>();
     293           10 :       cfg->getView().version = version;
     294              : 
     295           10 :       auto result = ifc.saveActiveVersion(cfg.get());
     296              : 
     297           10 :       if (result == 0) {
     298           10 :         successCount++;
     299              :       } else {
     300            0 :         failCount++;
     301            0 :         std::cout << "  Failed to store version " << version << "\n";
     302              :       }
     303           10 :     } catch (std::exception const& e) {
     304            0 :       failCount++;
     305            0 :       std::cout << "  Exception storing version " << version << ": " << e.what() << "\n";
     306            0 :     }
     307              :   }
     308              : 
     309            2 :   std::cout << "Results: " << successCount << "/" << NUM_VERSIONS << " successful\n";
     310              : 
     311            2 :   BOOST_CHECK_EQUAL(successCount, NUM_VERSIONS);
     312            2 :   BOOST_CHECK_EQUAL(failCount, 0);
     313              : 
     314            2 :   auto versions = ifc.getVersions(std::make_shared<TestConfiguration001>().get());
     315            2 :   int foundCount = 0;
     316           12 :   for (int i = 0; i < NUM_VERSIONS; ++i) {
     317           10 :     int version = fixture.baseVersion + 50 + i;
     318           10 :     if (versions.find(version) != versions.end()) {
     319           10 :       foundCount++;
     320              :     }
     321              :   }
     322              : 
     323            2 :   BOOST_CHECK_EQUAL(foundCount, NUM_VERSIONS);
     324            2 :   std::cout << "  Verified: All " << NUM_VERSIONS << " versions exist\n";
     325            2 : }
     326              : 
     327            4 : BOOST_AUTO_TEST_CASE(concurrent_composition_creation) {
     328            2 :   std::cout << "\n=== TEST 6: Concurrent composition creation ===\n";
     329              : 
     330            2 :   if (isFileSystemDB()) {
     331            1 :     std::cout << "  SKIPPED: FileSystemDB has additional race conditions beyond composition mutex\n";
     332            1 :     std::cout << "  See NEED_TO_IMPROVE.md for details\n";
     333            1 :     BOOST_CHECK(true);
     334            1 :     return;
     335              :   }
     336              : 
     337            1 :   auto ifc = DatabaseConfigurationInterface();
     338              : 
     339            1 :   const int NUM_THREADS = 5;
     340              : 
     341            1 :   std::vector<int> uniqueVersions;
     342            6 :   for (int i = 0; i < NUM_THREADS; ++i) {
     343            5 :     int version = fixture.baseVersion + 60 + i;
     344            5 :     uniqueVersions.push_back(version);
     345            5 :     BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, version), 0);
     346              :   }
     347              : 
     348            1 :   std::cout << "Setup: Created " << NUM_THREADS << " unique versions for compositions\n";
     349              : 
     350            1 :   std::vector<std::thread> threads;
     351            1 :   std::atomic<int> successCount{0};
     352            1 :   std::atomic<int> failCount{0};
     353            1 :   std::vector<std::string> compNames;
     354            1 :   std::mutex outputMutex;
     355              : 
     356            6 :   for (int i = 0; i < NUM_THREADS; ++i) {
     357            5 :     compNames.push_back("ConcurrentComp_" + std::to_string(fixture.baseVersion) + "_" + std::to_string(i));
     358              :   }
     359              : 
     360            1 :   std::cout << "Launching " << NUM_THREADS << " concurrent composition creation threads...\n";
     361              : 
     362            6 :   for (int i = 0; i < NUM_THREADS; ++i) {
     363           10 :     threads.emplace_back([&, i]() {
     364              :       try {
     365            5 :         auto localIfc = DatabaseConfigurationInterface();
     366              : 
     367            5 :         auto map = DatabaseConfigurationInterface::config_version_map_t{};
     368            5 :         map[fixture.config001Name()] = uniqueVersions[i];
     369              : 
     370            5 :         localIfc.storeGlobalConfiguration(map, compNames[i]);
     371            5 :         successCount++;
     372              : 
     373            5 :       } catch (std::exception const& e) {
     374            0 :         failCount++;
     375            0 :         std::lock_guard<std::mutex> lock(outputMutex);
     376            0 :         std::cout << "  Thread " << i << " exception: " << e.what() << "\n";
     377            0 :       }
     378            5 :     });
     379              :   }
     380              : 
     381            6 :   for (auto& t : threads) {
     382            5 :     t.join();
     383              :   }
     384              : 
     385            1 :   std::cout << "Results: " << successCount << "/" << NUM_THREADS << " successful\n";
     386              : 
     387            2 :   BOOST_CHECK_EQUAL(successCount.load(), NUM_THREADS);
     388            2 :   BOOST_CHECK_EQUAL(failCount.load(), 0);
     389              : 
     390            1 :   auto allComps = ifc.findAllGlobalConfigurations();
     391            1 :   int foundCount = 0;
     392            6 :   for (auto const& name : compNames) {
     393            5 :     if (allComps.find(name) != allComps.end()) {
     394            5 :       foundCount++;
     395              :     }
     396              :   }
     397              : 
     398            1 :   BOOST_CHECK_EQUAL(foundCount, NUM_THREADS);
     399            1 :   std::cout << "  Verified: All " << NUM_THREADS << " compositions exist\n";
     400              : 
     401            6 :   for (int i = 0; i < NUM_THREADS; ++i) {
     402            5 :     auto loaded = ifc.loadGlobalConfiguration(compNames[i]);
     403            5 :     BOOST_CHECK_EQUAL(loaded.size(), 1);
     404            5 :     BOOST_CHECK_EQUAL(loaded[fixture.config001Name()], uniqueVersions[i]);
     405            5 :   }
     406            1 :   std::cout << "  Verified: Each composition has its unique version\n";
     407            1 : }
     408              : 
     409            4 : BOOST_AUTO_TEST_CASE(concurrent_reverse_lookups) {
     410            2 :   std::cout << "\n=== TEST 7: Concurrent reverse lookups ===\n";
     411              : 
     412            2 :   auto ifc = DatabaseConfigurationInterface();
     413              : 
     414            2 :   int targetVersion = fixture.baseVersion + 70;
     415            2 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, targetVersion), 0);
     416              : 
     417            2 :   auto map = DatabaseConfigurationInterface::config_version_map_t{};
     418            2 :   map[fixture.config001Name()] = targetVersion;
     419              : 
     420            2 :   std::string compName = "ReverseLookupTarget_" + std::to_string(fixture.baseVersion);
     421            2 :   ifc.storeGlobalConfiguration(map, compName);
     422              : 
     423            2 :   std::cout << "Setup: Created composition " << compName << "\n";
     424              : 
     425            2 :   const int NUM_THREADS = 10;
     426            2 :   std::vector<std::thread> threads;
     427            2 :   std::atomic<int> successCount{0};
     428            2 :   std::atomic<int> failCount{0};
     429              : 
     430            2 :   std::cout << "Launching " << NUM_THREADS << " concurrent reverse lookup threads...\n";
     431              : 
     432           22 :   for (int i = 0; i < NUM_THREADS; ++i) {
     433           40 :     threads.emplace_back([&]() {
     434              :       try {
     435           20 :         auto localIfc = DatabaseConfigurationInterface();
     436           20 :         auto found = localIfc.findCompositionsContaining(fixture.config001Name(), std::to_string(targetVersion));
     437              : 
     438           20 :         if (found.find(compName) != found.end()) {
     439           20 :           successCount++;
     440              :         } else {
     441            0 :           failCount++;
     442              :         }
     443           20 :       } catch (std::exception const& e) {
     444            0 :         failCount++;
     445            0 :       }
     446           20 :     });
     447              :   }
     448              : 
     449           22 :   for (auto& t : threads) {
     450           20 :     t.join();
     451              :   }
     452              : 
     453            2 :   std::cout << "Results: " << successCount << "/" << NUM_THREADS << " found target\n";
     454              : 
     455            4 :   BOOST_CHECK_EQUAL(successCount.load(), NUM_THREADS);
     456            4 :   BOOST_CHECK_EQUAL(failCount.load(), 0);
     457              : 
     458            2 :   std::cout << "  Verified: Concurrent reverse lookups work\n";
     459            2 : }
     460              : 
     461            4 : BOOST_AUTO_TEST_CASE(mt_store_correctness) {
     462            2 :   std::cout << "\n=== TEST 8: MT store correctness verification ===\n";
     463              : 
     464            2 :   if (isFileSystemDB()) {
     465            1 :     std::cout << "  SKIPPED: FileSystemDB has race conditions in configurationComposition\n";
     466            1 :     BOOST_CHECK(true);
     467            1 :     return;
     468              :   }
     469              : 
     470            1 :   auto ifc = DatabaseConfigurationInterface();
     471              : 
     472            1 :   int v1 = fixture.baseVersion + 80;
     473            1 :   int v2 = fixture.baseVersion + 81;
     474              : 
     475            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, v1), 0);
     476            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig002(ifc, v2), 0);
     477              : 
     478            1 :   auto map = DatabaseConfigurationInterface::config_version_map_t{};
     479            1 :   map[fixture.config001Name()] = v1;
     480            1 :   map[fixture.config002Name()] = v2;
     481              : 
     482            1 :   std::string compName = "MTCorrectness_" + std::to_string(fixture.baseVersion);
     483              : 
     484            1 :   ifc.storeGlobalConfiguration_mt(map, compName);
     485              : 
     486            1 :   auto loaded = ifc.loadGlobalConfiguration(compName);
     487              : 
     488            1 :   BOOST_CHECK_EQUAL(loaded.size(), map.size());
     489              : 
     490            3 :   for (auto const& entry : map) {
     491            2 :     auto it = loaded.find(entry.first);
     492            2 :     BOOST_CHECK_MESSAGE(it != loaded.end(), "Missing member: " << entry.first);
     493            2 :     if (it != loaded.end()) {
     494            2 :       BOOST_CHECK_EQUAL(it->second, entry.second);
     495              :     }
     496              :   }
     497              : 
     498            1 :   std::cout << "  Verified: All " << map.size() << " members stored correctly\n";
     499              : 
     500            3 :   for (auto const& entry : map) {
     501            2 :     auto found = ifc.findCompositionsContaining(entry.first, std::to_string(entry.second));
     502            2 :     BOOST_CHECK_MESSAGE(found.find(compName) != found.end(), "Reverse lookup failed for " << entry.first << "/" << entry.second);
     503            2 :   }
     504              : 
     505            1 :   std::cout << "  Verified: All reverse lookups work\n";
     506            1 : }
     507              : 
     508            4 : BOOST_AUTO_TEST_CASE(mt_store_empty_composition) {
     509            2 :   std::cout << "\n=== TEST 9: MT store empty composition rejected ===\n";
     510              : 
     511            2 :   auto ifc = DatabaseConfigurationInterface();
     512              : 
     513            2 :   auto emptyMap = DatabaseConfigurationInterface::config_version_map_t{};
     514            2 :   std::string compName = "MTEmptyComp_" + std::to_string(fixture.baseVersion);
     515              : 
     516            2 :   std::cout << "Attempting to create empty composition with storeGlobalConfiguration_mt...\n";
     517              : 
     518            4 :   BOOST_CHECK_THROW(ifc.storeGlobalConfiguration_mt(emptyMap, compName), std::exception);
     519            2 :   std::cout << "  Result: Exception thrown (as expected)\n";
     520            2 :   std::cout << "  Verified: Empty compositions rejected with clear error\n";
     521            2 : }
     522              : 
     523            4 : BOOST_AUTO_TEST_CASE(mt_store_stress_test) {
     524            2 :   std::cout << "\n=== TEST 10: MT store stress test ===\n";
     525              : 
     526            2 :   if (isFileSystemDB()) {
     527            1 :     std::cout << "  SKIPPED: FileSystemDB has race conditions in configurationComposition\n";
     528            1 :     BOOST_CHECK(true);
     529            1 :     return;
     530              :   }
     531              : 
     532            1 :   auto ifc = DatabaseConfigurationInterface();
     533              : 
     534            1 :   int v1 = fixture.baseVersion + 100;
     535            1 :   BOOST_REQUIRE_EQUAL(fixture.createConfig001(ifc, v1), 0);
     536              : 
     537            1 :   const int NUM_COMPOSITIONS = 10;
     538            1 :   std::vector<std::string> compNames;
     539              : 
     540            1 :   auto map = DatabaseConfigurationInterface::config_version_map_t{};
     541            1 :   map[fixture.config001Name()] = v1;
     542              : 
     543            1 :   std::cout << "Creating " << NUM_COMPOSITIONS << " compositions rapidly...\n";
     544              : 
     545            1 :   auto start = std::chrono::high_resolution_clock::now();
     546              : 
     547           11 :   for (int i = 0; i < NUM_COMPOSITIONS; ++i) {
     548           10 :     std::string compName = "MTStress_" + std::to_string(fixture.baseVersion) + "_" + std::to_string(i);
     549           10 :     compNames.push_back(compName);
     550           10 :     ifc.storeGlobalConfiguration_mt(map, compName);
     551           10 :   }
     552              : 
     553            1 :   auto end = std::chrono::high_resolution_clock::now();
     554            1 :   auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
     555              : 
     556            1 :   std::cout << "  Created " << NUM_COMPOSITIONS << " compositions in " << duration.count() << "ms\n";
     557            1 :   std::cout << "  Average: " << (duration.count() / NUM_COMPOSITIONS) << "ms per composition\n";
     558              : 
     559            1 :   auto allComps = ifc.findAllGlobalConfigurations();
     560            1 :   int foundCount = 0;
     561           11 :   for (auto const& name : compNames) {
     562           10 :     if (allComps.find(name) != allComps.end()) {
     563           10 :       foundCount++;
     564              :     }
     565              :   }
     566              : 
     567            1 :   BOOST_CHECK_EQUAL(foundCount, NUM_COMPOSITIONS);
     568            1 :   std::cout << "  Verified: All " << NUM_COMPOSITIONS << " compositions exist\n";
     569            1 : }
     570              : 
     571              : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1