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()
|