Skip to content

Commit 7f71a29

Browse files
test: Add more concurrency tests
1 parent 124c3af commit 7f71a29

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

tests/basic/test_operations.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <gtest/gtest.h>
44
#include <thread>
55
#include <type_traits>
6+
#include <utility>
67
#include <vector>
78

89
import mcpplibs.primitives;
@@ -196,6 +197,122 @@ TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) {
196197
EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread);
197198
}
198199

200+
TEST(OperationsTest,
201+
BinaryOperationsWithLoadStoreRemainStableUnderHighConcurrency) {
202+
using value_t =
203+
primitive<int, policy::value::checked, policy::concurrency::fenced,
204+
policy::error::expected>;
205+
206+
constexpr int kWriterThreads = 6;
207+
constexpr int kReaderThreads = 8;
208+
constexpr int kIterationsPerThread = 25000;
209+
constexpr int kMaxOperand = 100000;
210+
211+
auto lhs = value_t{0};
212+
auto rhs = value_t{0};
213+
auto sink = value_t{0};
214+
215+
std::atomic<int> add_error_count{0};
216+
std::atomic<int> sub_error_count{0};
217+
std::atomic<int> range_violation_count{0};
218+
std::atomic<bool> start{false};
219+
220+
std::vector<std::thread> workers;
221+
workers.reserve(kWriterThreads + kReaderThreads);
222+
223+
for (int writer = 0; writer < kWriterThreads; ++writer) {
224+
workers.emplace_back([&, writer]() {
225+
while (!start.load(std::memory_order_acquire)) {
226+
}
227+
228+
for (int n = 0; n < kIterationsPerThread; ++n) {
229+
auto const v1 = (writer + n) % (kMaxOperand + 1);
230+
auto const v2 = (writer * 3 + n * 7) % (kMaxOperand + 1);
231+
lhs.store(v1);
232+
rhs.store(v2);
233+
}
234+
});
235+
}
236+
237+
for (int reader = 0; reader < kReaderThreads; ++reader) {
238+
workers.emplace_back([&, reader]() {
239+
while (!start.load(std::memory_order_acquire)) {
240+
}
241+
242+
for (int n = 0; n < kIterationsPerThread; ++n) {
243+
if (((reader + n) & 1) == 0) {
244+
auto const out = operations::add(lhs, rhs);
245+
if (!out.has_value()) {
246+
add_error_count.fetch_add(1, std::memory_order_relaxed);
247+
continue;
248+
}
249+
250+
auto const v = out->load();
251+
if (v < 0 || v > (kMaxOperand * 2)) {
252+
range_violation_count.fetch_add(1, std::memory_order_relaxed);
253+
}
254+
sink.store(v);
255+
auto const snapshot = sink.load();
256+
if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) {
257+
range_violation_count.fetch_add(1, std::memory_order_relaxed);
258+
}
259+
continue;
260+
}
261+
262+
auto const out = operations::sub(lhs, rhs);
263+
if (!out.has_value()) {
264+
sub_error_count.fetch_add(1, std::memory_order_relaxed);
265+
continue;
266+
}
267+
268+
auto const v = out->load();
269+
if (v < -kMaxOperand || v > kMaxOperand) {
270+
range_violation_count.fetch_add(1, std::memory_order_relaxed);
271+
}
272+
sink.store(v);
273+
auto const snapshot = sink.load();
274+
if (snapshot < -kMaxOperand || snapshot > (kMaxOperand * 2)) {
275+
range_violation_count.fetch_add(1, std::memory_order_relaxed);
276+
}
277+
}
278+
});
279+
}
280+
281+
start.store(true, std::memory_order_release);
282+
283+
for (auto &worker : workers) {
284+
worker.join();
285+
}
286+
287+
EXPECT_EQ(add_error_count.load(std::memory_order_relaxed), 0);
288+
EXPECT_EQ(sub_error_count.load(std::memory_order_relaxed), 0);
289+
EXPECT_EQ(range_violation_count.load(std::memory_order_relaxed), 0);
290+
}
291+
292+
TEST(OperationsTest, PrimitiveSupportsCopyAndMoveSpecialMembers) {
293+
using value_t = primitive<int, policy::value::checked, policy::error::expected>;
294+
295+
static_assert(std::is_copy_constructible_v<value_t>);
296+
static_assert(std::is_copy_assignable_v<value_t>);
297+
static_assert(std::is_move_constructible_v<value_t>);
298+
static_assert(std::is_move_assignable_v<value_t>);
299+
300+
auto original = value_t{42};
301+
auto copy_constructed = value_t{original};
302+
EXPECT_EQ(copy_constructed.load(), 42);
303+
304+
auto copy_assigned = value_t{0};
305+
copy_assigned = original;
306+
EXPECT_EQ(copy_assigned.load(), 42);
307+
308+
auto move_constructed = value_t{std::move(copy_assigned)};
309+
EXPECT_EQ(move_constructed.load(), 42);
310+
311+
auto move_assigned = value_t{0};
312+
move_assigned = std::move(move_constructed);
313+
EXPECT_EQ(move_assigned.load(), 42);
314+
}
315+
199316
TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) {
200317
using lhs_t = primitive<int, policy::value::checked, policy::type::strict,
201318
policy::error::expected>;

0 commit comments

Comments
 (0)