@@ -95,11 +95,82 @@ namespace
95
95
auto operator <=>(const ValueType&) const noexcept = default ;
96
96
};
97
97
98
+ // NOTE: We do not use TEMPLATE_LIST_TEST_CASE here because code coverage tools (such as gcov/lcov)
99
+ // do not properly attribute coverage to tests instantiated via TEMPLATE_LIST_TEST_CASE. Instead,
100
+ // we use individual TEMPLATE_TEST_CASEs for each mutex type, and factorize the test logic into
101
+ // function templates to avoid code duplication. This ensures accurate code coverage reporting.
102
+
98
103
using supported_mutex_types = std::tuple<std::mutex, std::shared_mutex, std::recursive_mutex>;
99
104
100
- TEMPLATE_LIST_TEST_CASE (" synchronized_value basics" , " [template][thread-safe]" , supported_mutex_types)
105
+ template <mamba::util::Mutex M>
106
+ auto test_concurrent_increment (
107
+ std::invocable<mamba::util::synchronized_value<ValueType, M>&> auto increment_task
108
+ )
101
109
{
102
- using synchronized_value = mamba::util::synchronized_value<ValueType, TestType>;
110
+ static constexpr auto arbitrary_number_of_executing_threads = 512 ;
111
+
112
+ mamba::util::synchronized_value<ValueType, M> current_value;
113
+ static constexpr int expected_result = arbitrary_number_of_executing_threads;
114
+
115
+ std::atomic<bool > run_tasks = false ; // used to launch tasks about the same time, simpler
116
+ // than condition_variable
117
+ std::vector<std::future<void >> tasks;
118
+
119
+ // Launch the reading and writing tasks (maybe threads, depends on how async is implemented)
120
+ for (int i = 0 ; i < expected_result * 2 ; ++i)
121
+ {
122
+ if (i % 2 ) // intertwine reading and writing tasks
123
+ {
124
+ // add writing task
125
+ tasks.push_back (std::async (
126
+ std::launch::async,
127
+ [&, increment_task]
128
+ {
129
+ // don't actually run until we get the green light
130
+ mambatests::wait_condition ([&] { return run_tasks == true ; });
131
+ increment_task (current_value);
132
+ }
133
+ ));
134
+ }
135
+ else
136
+ {
137
+ // add reading task
138
+ tasks.push_back (std::async (
139
+ std::launch::async,
140
+ [&]
141
+ {
142
+ // don't actually run until we get the green light
143
+ mambatests::wait_condition ([&] { return run_tasks == true ; });
144
+ const auto & readonly_value = std::as_const (current_value);
145
+ static constexpr auto arbitrary_read_count = 100 ;
146
+ long long sum = 0 ;
147
+ for (int c = 0 ; c < arbitrary_read_count; ++c)
148
+ {
149
+ sum += readonly_value->x ; // TODO: also try to mix reading and writing
150
+ // using different kinds of access
151
+ std::this_thread::yield (); // for timing randomness and limit
152
+ // over-exhaustion
153
+ }
154
+ REQUIRE (sum != 0 ); // It is possible but extremely unlikely that all
155
+ // reading tasks will read before any writing tasks.
156
+ }
157
+ ));
158
+ }
159
+ }
160
+
161
+ run_tasks = true ; // green light, tasks will run probably concurrently, worse case in
162
+ // unpredictable order
163
+ for (auto & task : tasks)
164
+ {
165
+ task.wait (); // wait all to be finished
166
+ }
167
+
168
+ REQUIRE (current_value->x == expected_result);
169
+ }
170
+
171
+ template <typename MutexType>
172
+ void test_synchronized_value_basics () {
173
+ using synchronized_value = mamba::util::synchronized_value<ValueType, MutexType>;
103
174
104
175
SECTION (" default constructible" )
105
176
{
@@ -208,115 +279,53 @@ namespace
208
279
}
209
280
}
210
281
211
- TEMPLATE_LIST_TEST_CASE (
212
- " synchronized_value initializer-list" ,
213
- " [template][thread-safe]" ,
214
- supported_mutex_types
215
- )
282
+ TEMPLATE_TEST_CASE (" synchronized_value basics with std::mutex" , " [template][thread-safe]" , std::mutex)
216
283
{
217
- using synchronized_value = mamba::util::synchronized_value<std::vector<int >, TestType>;
218
- synchronized_value values{ 1 , 2 , 3 , 4 };
284
+ test_synchronized_value_basics<std::mutex>();
285
+ }
286
+
287
+ TEMPLATE_TEST_CASE (" synchronized_value basics with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
288
+ {
289
+ test_synchronized_value_basics<std::shared_mutex>();
219
290
}
220
291
221
- TEMPLATE_LIST_TEST_CASE (" synchronized_value apply example " , " [template][thread-safe]" , supported_mutex_types )
292
+ TEMPLATE_TEST_CASE (" synchronized_value basics with std::recursive_mutex " , " [template][thread-safe]" , std::recursive_mutex )
222
293
{
223
- using synchronized_value = mamba::util::synchronized_value<std::vector<int >, TestType>;
294
+ test_synchronized_value_basics<std::recursive_mutex>();
295
+ }
296
+
297
+ // Factorized initializer-list test
298
+ template <typename MutexType>
299
+ void test_synchronized_value_initializer_list () {
300
+ using synchronized_value = mamba::util::synchronized_value<std::vector<int >, MutexType>;
301
+ synchronized_value values{ 1 , 2 , 3 , 4 };
302
+ }
224
303
304
+ // Factorized apply example test
305
+ template <typename MutexType>
306
+ void test_synchronized_value_apply_example () {
307
+ using synchronized_value = mamba::util::synchronized_value<std::vector<int >, MutexType>;
225
308
const std::vector initial_values{ 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 };
226
309
const std::vector sorted_values{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
227
-
228
310
synchronized_value values{ initial_values };
229
311
values.apply (std::ranges::sort);
230
312
REQUIRE (values == sorted_values);
231
313
values.apply (std::ranges::sort, std::ranges::greater{});
232
314
REQUIRE (values == initial_values);
233
315
}
234
316
235
- template <mamba::util::Mutex M>
236
- auto test_concurrent_increment (
237
- std::invocable<mamba::util::synchronized_value<ValueType, M>&> auto increment_task
238
- )
239
- {
240
- static constexpr auto arbitrary_number_of_executing_threads = 512 ;
241
-
242
- mamba::util::synchronized_value<ValueType, M> current_value;
243
- static constexpr int expected_result = arbitrary_number_of_executing_threads;
244
-
245
- std::atomic<bool > run_tasks = false ; // used to launch tasks about the same time, simpler
246
- // than condition_variable
247
- std::vector<std::future<void >> tasks;
248
-
249
- // Launch the reading and writing tasks (maybe threads, depends on how async is implemented)
250
- for (int i = 0 ; i < expected_result * 2 ; ++i)
251
- {
252
- if (i % 2 ) // intertwine reading and writing tasks
253
- {
254
- // add writing task
255
- tasks.push_back (std::async (
256
- std::launch::async,
257
- [&, increment_task]
258
- {
259
- // don't actually run until we get the green light
260
- mambatests::wait_condition ([&] { return run_tasks == true ; });
261
- increment_task (current_value);
262
- }
263
- ));
264
- }
265
- else
266
- {
267
- // add reading task
268
- tasks.push_back (std::async (
269
- std::launch::async,
270
- [&]
271
- {
272
- // don't actually run until we get the green light
273
- mambatests::wait_condition ([&] { return run_tasks == true ; });
274
- const auto & readonly_value = std::as_const (current_value);
275
- static constexpr auto arbitrary_read_count = 100 ;
276
- long long sum = 0 ;
277
- for (int c = 0 ; c < arbitrary_read_count; ++c)
278
- {
279
- sum += readonly_value->x ; // TODO: also try to mix reading and writing
280
- // using different kinds of access
281
- std::this_thread::yield (); // for timing randomness and limit
282
- // over-exhaustion
283
- }
284
- REQUIRE (sum != 0 ); // It is possible but extremely unlikely that all
285
- // reading tasks will read before any writing tasks.
286
- }
287
- ));
288
- }
289
- }
290
-
291
-
292
- run_tasks = true ; // green light, tasks will run probably concurrently, worse case in
293
- // unpredictable order
294
- for (auto & task : tasks)
295
- {
296
- task.wait (); // wait all to be finished
297
- }
298
-
299
- REQUIRE (current_value->x == expected_result);
300
- }
301
-
302
- TEMPLATE_LIST_TEST_CASE (
303
- " synchronized_value thread-safe direct_access" ,
304
- " [template][thread-safe]" ,
305
- supported_mutex_types
306
- )
307
- {
308
- using synchronized_value = mamba::util::synchronized_value<ValueType, TestType>;
309
- test_concurrent_increment<TestType>([](synchronized_value& sv) { sv->x += 1 ; });
317
+ // Factorized thread-safe direct_access test
318
+ template <typename MutexType>
319
+ void test_synchronized_value_threadsafe_direct_access () {
320
+ using synchronized_value = mamba::util::synchronized_value<ValueType, MutexType>;
321
+ test_concurrent_increment<MutexType>([](synchronized_value& sv) { sv->x += 1 ; });
310
322
}
311
323
312
- TEMPLATE_LIST_TEST_CASE (
313
- " synchronized_value thread-safe synchronize" ,
314
- " [template][thread-safe]" ,
315
- supported_mutex_types
316
- )
317
- {
318
- using synchronized_value = mamba::util::synchronized_value<ValueType, TestType>;
319
- test_concurrent_increment<TestType>(
324
+ // Factorized thread-safe synchronize test
325
+ template <typename MutexType>
326
+ void test_synchronized_value_threadsafe_synchronize () {
327
+ using synchronized_value = mamba::util::synchronized_value<ValueType, MutexType>;
328
+ test_concurrent_increment<MutexType>(
320
329
[](synchronized_value& sv)
321
330
{
322
331
auto synched_sv = sv.synchronize ();
@@ -325,26 +334,20 @@ namespace
325
334
);
326
335
}
327
336
328
- TEMPLATE_LIST_TEST_CASE (
329
- " synchronized_value thread-safe apply" ,
330
- " [template][thread-safe]" ,
331
- supported_mutex_types
332
- )
333
- {
334
- using synchronized_value = mamba::util::synchronized_value<ValueType, TestType>;
335
- test_concurrent_increment<TestType>([](synchronized_value& sv)
336
- { sv.apply ([](ValueType& value) { value.x += 1 ; }); });
337
+ // Factorized thread-safe apply test
338
+ template <typename MutexType>
339
+ void test_synchronized_value_threadsafe_apply () {
340
+ using synchronized_value = mamba::util::synchronized_value<ValueType, MutexType>;
341
+ test_concurrent_increment<MutexType>([](synchronized_value& sv)
342
+ { sv.apply ([](ValueType& value) { value.x += 1 ; }); });
337
343
}
338
344
339
- TEMPLATE_LIST_TEST_CASE (
340
- " synchronized_value thread-safe multiple synchronize" ,
341
- " [template][thread-safe]" ,
342
- supported_mutex_types
343
- )
344
- {
345
- using synchronized_value = mamba::util::synchronized_value<ValueType, TestType>;
345
+ // Factorized thread-safe multiple synchronize test
346
+ template <typename MutexType>
347
+ void test_synchronized_value_threadsafe_multiple_synchronize () {
348
+ using synchronized_value = mamba::util::synchronized_value<ValueType, MutexType>;
346
349
const mamba::util::synchronized_value<std::vector<int >, std::shared_mutex> extra_values{ 1 };
347
- test_concurrent_increment<TestType >(
350
+ test_concurrent_increment<MutexType >(
348
351
[&](synchronized_value& sv)
349
352
{
350
353
auto [ssv, sev] = synchronize (sv, extra_values);
@@ -353,6 +356,85 @@ namespace
353
356
);
354
357
}
355
358
359
+ // Now call these from the TEMPLATE_TEST_CASEs
360
+ TEMPLATE_TEST_CASE (" synchronized_value initializer-list with std::mutex" , " [template][thread-safe]" , std::mutex)
361
+ {
362
+ test_synchronized_value_initializer_list<std::mutex>();
363
+ }
364
+ TEMPLATE_TEST_CASE (" synchronized_value initializer-list with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
365
+ {
366
+ test_synchronized_value_initializer_list<std::shared_mutex>();
367
+ }
368
+ TEMPLATE_TEST_CASE (" synchronized_value initializer-list with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
369
+ {
370
+ test_synchronized_value_initializer_list<std::recursive_mutex>();
371
+ }
372
+
373
+ TEMPLATE_TEST_CASE (" synchronized_value apply example with std::mutex" , " [template][thread-safe]" , std::mutex)
374
+ {
375
+ test_synchronized_value_apply_example<std::mutex>();
376
+ }
377
+ TEMPLATE_TEST_CASE (" synchronized_value apply example with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
378
+ {
379
+ test_synchronized_value_apply_example<std::shared_mutex>();
380
+ }
381
+ TEMPLATE_TEST_CASE (" synchronized_value apply example with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
382
+ {
383
+ test_synchronized_value_apply_example<std::recursive_mutex>();
384
+ }
385
+
386
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe direct_access with std::mutex" , " [template][thread-safe]" , std::mutex)
387
+ {
388
+ test_synchronized_value_threadsafe_direct_access<std::mutex>();
389
+ }
390
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe direct_access with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
391
+ {
392
+ test_synchronized_value_threadsafe_direct_access<std::shared_mutex>();
393
+ }
394
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe direct_access with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
395
+ {
396
+ test_synchronized_value_threadsafe_direct_access<std::recursive_mutex>();
397
+ }
398
+
399
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe synchronize with std::mutex" , " [template][thread-safe]" , std::mutex)
400
+ {
401
+ test_synchronized_value_threadsafe_synchronize<std::mutex>();
402
+ }
403
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe synchronize with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
404
+ {
405
+ test_synchronized_value_threadsafe_synchronize<std::shared_mutex>();
406
+ }
407
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe synchronize with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
408
+ {
409
+ test_synchronized_value_threadsafe_synchronize<std::recursive_mutex>();
410
+ }
411
+
412
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe apply with std::mutex" , " [template][thread-safe]" , std::mutex)
413
+ {
414
+ test_synchronized_value_threadsafe_apply<std::mutex>();
415
+ }
416
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe apply with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
417
+ {
418
+ test_synchronized_value_threadsafe_apply<std::shared_mutex>();
419
+ }
420
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe apply with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
421
+ {
422
+ test_synchronized_value_threadsafe_apply<std::recursive_mutex>();
423
+ }
424
+
425
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe multiple synchronize with std::mutex" , " [template][thread-safe]" , std::mutex)
426
+ {
427
+ test_synchronized_value_threadsafe_multiple_synchronize<std::mutex>();
428
+ }
429
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe multiple synchronize with std::shared_mutex" , " [template][thread-safe]" , std::shared_mutex)
430
+ {
431
+ test_synchronized_value_threadsafe_multiple_synchronize<std::shared_mutex>();
432
+ }
433
+ TEMPLATE_TEST_CASE (" synchronized_value thread-safe multiple synchronize with std::recursive_mutex" , " [template][thread-safe]" , std::recursive_mutex)
434
+ {
435
+ test_synchronized_value_threadsafe_multiple_synchronize<std::recursive_mutex>();
436
+ }
437
+
356
438
TEST_CASE (" synchronized_value basics multiple synchronize" )
357
439
{
358
440
using namespace mamba ::util;
0 commit comments