Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <everest/util/queue/simple_queue.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
using namespace everest::lib::util;
|
||||
|
||||
// =================================================================
|
||||
// 2. Test Fixture Setup
|
||||
// =================================================================
|
||||
|
||||
template <typename T> class SimpleQueueTest : public ::testing::Test {
|
||||
protected:
|
||||
simple_queue<T> queue;
|
||||
};
|
||||
|
||||
// Typed Test Suite for standard types
|
||||
using QueueTypes = ::testing::Types<int, std::string>;
|
||||
TYPED_TEST_SUITE(SimpleQueueTest, QueueTypes);
|
||||
|
||||
// =================================================================
|
||||
// A. Basic Functionality Tests (FIFO & Empty Checks)
|
||||
// =================================================================
|
||||
|
||||
TYPED_TEST(SimpleQueueTest, InitialStateIsEmpty) {
|
||||
ASSERT_TRUE(this->queue.empty());
|
||||
ASSERT_EQ(this->queue.size(), 0);
|
||||
ASSERT_FALSE(this->queue.pop().has_value());
|
||||
}
|
||||
|
||||
TYPED_TEST(SimpleQueueTest, PushAndEmptyCheck) {
|
||||
TypeParam value;
|
||||
|
||||
// Use if constexpr to initialize value correctly
|
||||
if constexpr (std::is_same_v<TypeParam, int>) {
|
||||
value = 10;
|
||||
} else if constexpr (std::is_same_v<TypeParam, std::string>) {
|
||||
value = "Test_10";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this->queue.push(value);
|
||||
|
||||
ASSERT_FALSE(this->queue.empty());
|
||||
ASSERT_EQ(this->queue.size(), 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(SimpleQueueTest, PushPopAndEmptyCheck) {
|
||||
TypeParam expected_value;
|
||||
|
||||
// Use if constexpr to initialize value correctly
|
||||
if constexpr (std::is_same_v<TypeParam, int>) {
|
||||
expected_value = 42;
|
||||
} else if constexpr (std::is_same_v<TypeParam, std::string>) {
|
||||
expected_value = "Test_42";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this->queue.push(expected_value);
|
||||
|
||||
std::optional<TypeParam> result = this->queue.pop();
|
||||
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(result.value(), expected_value);
|
||||
ASSERT_TRUE(this->queue.empty());
|
||||
ASSERT_EQ(this->queue.size(), 0);
|
||||
}
|
||||
|
||||
TYPED_TEST(SimpleQueueTest, MultiplePushAndPopOrder) {
|
||||
const int count = 3;
|
||||
|
||||
// Push elements (0, 1, 2)
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if constexpr (std::is_same_v<TypeParam, int>) {
|
||||
this->queue.push(i);
|
||||
} else {
|
||||
this->queue.push(std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Pop elements and verify FIFO order (0, 1, 2)
|
||||
for (int i = 0; i < count; ++i) {
|
||||
std::optional<TypeParam> result = this->queue.pop();
|
||||
TypeParam expected_value;
|
||||
if constexpr (std::is_same_v<TypeParam, int>) {
|
||||
expected_value = i;
|
||||
} else {
|
||||
expected_value = std::to_string(i);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(result.value(), expected_value) << "Element popped out of FIFO order.";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(this->queue.empty());
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// B. Reference Tests (front() and back())
|
||||
// =================================================================
|
||||
|
||||
TYPED_TEST(SimpleQueueTest, FrontAndBackReferences) {
|
||||
TypeParam val1, val2;
|
||||
|
||||
if constexpr (std::is_same_v<TypeParam, int>) {
|
||||
val1 = 100;
|
||||
val2 = 200;
|
||||
} else if constexpr (std::is_same_v<TypeParam, std::string>) {
|
||||
val1 = "Front";
|
||||
val2 = "Back";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this->queue.push(val1);
|
||||
this->queue.push(val2);
|
||||
|
||||
// Verify front()
|
||||
ASSERT_EQ(this->queue.front(), val1);
|
||||
|
||||
// Verify back()
|
||||
ASSERT_EQ(this->queue.back(), val2);
|
||||
|
||||
// After pop, front should change
|
||||
this->queue.pop();
|
||||
ASSERT_EQ(this->queue.front(), val2);
|
||||
ASSERT_EQ(this->queue.back(), val2);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// C. Move-Only Type Compatibility Test (Verifying the pop() fix)
|
||||
// =================================================================
|
||||
|
||||
// Test suite for std::unique_ptr<int> (a move-only type)
|
||||
class SimpleQueueMoveOnlyTest : public ::testing::Test {
|
||||
protected:
|
||||
simple_queue<std::unique_ptr<int>> queue;
|
||||
};
|
||||
|
||||
TEST_F(SimpleQueueMoveOnlyTest, PushAndPopMoveOnlyType) {
|
||||
const int value1 = 10;
|
||||
const int value2 = 20;
|
||||
|
||||
// Push: Requires the r-value push overload
|
||||
this->queue.push(std::make_unique<int>(value1));
|
||||
this->queue.push(std::make_unique<int>(value2));
|
||||
|
||||
ASSERT_EQ(this->queue.size(), 2);
|
||||
|
||||
// Pop: Requires the fixed move-based pop()
|
||||
std::optional<std::unique_ptr<int>> opt_result1 = this->queue.pop();
|
||||
|
||||
// Verify the value was retrieved
|
||||
ASSERT_TRUE(opt_result1.has_value());
|
||||
ASSERT_NE(opt_result1.value(), nullptr);
|
||||
ASSERT_EQ(*opt_result1.value(), value1);
|
||||
|
||||
// Pop the second item
|
||||
std::optional<std::unique_ptr<int>> opt_result2 = this->queue.pop();
|
||||
ASSERT_TRUE(opt_result2.has_value());
|
||||
ASSERT_EQ(*opt_result2.value(), value2);
|
||||
|
||||
ASSERT_TRUE(this->queue.empty());
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <everest/util/queue/thread_safe_bounded_queue.hpp>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace everest::lib::util;
|
||||
|
||||
/**
|
||||
* @brief Helper struct to mimic the TrackedAction used in the thread pool,
|
||||
* as the queue now expects a type with an .arrival member.
|
||||
*/
|
||||
struct TestTask {
|
||||
int value;
|
||||
std::chrono::steady_clock::time_point arrival;
|
||||
|
||||
explicit TestTask(int v = 0) : value(v), arrival(std::chrono::steady_clock::now()) {
|
||||
}
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// 1. Bounded Functionality Tests (Backpressure)
|
||||
// =================================================================
|
||||
|
||||
TEST(ThreadSafeBoundedQueueTest, PushBlocksWhenFull) {
|
||||
const size_t limit = 2;
|
||||
thread_safe_bounded_queue<TestTask> queue(limit);
|
||||
|
||||
// Fill the queue to the limit
|
||||
queue.push(TestTask(1));
|
||||
queue.push(TestTask(2));
|
||||
|
||||
std::atomic<bool> push_completed{false};
|
||||
std::thread producer([&] {
|
||||
// This should block until a consumer pops an item
|
||||
queue.push(TestTask(3));
|
||||
push_completed = true;
|
||||
});
|
||||
|
||||
// Give the thread a moment to start and block
|
||||
std::this_thread::sleep_for(50ms);
|
||||
ASSERT_FALSE(push_completed.load());
|
||||
|
||||
// Pop an item, which should unblock the producer
|
||||
auto popped = queue.try_pop(100ms);
|
||||
ASSERT_TRUE(popped.has_value());
|
||||
ASSERT_EQ(popped->value, 1);
|
||||
|
||||
producer.join();
|
||||
ASSERT_TRUE(push_completed.load());
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. Latency Interface Tests
|
||||
// =================================================================
|
||||
|
||||
TEST(ThreadSafeBoundedQueueTest, OldestArrivalTracking) {
|
||||
thread_safe_bounded_queue<TestTask> queue(10);
|
||||
|
||||
auto t1 = std::chrono::steady_clock::now();
|
||||
queue.push(TestTask(100));
|
||||
std::this_thread::sleep_for(10ms);
|
||||
|
||||
auto t2 = std::chrono::steady_clock::now();
|
||||
queue.push(TestTask(200));
|
||||
|
||||
auto oldest = queue.oldest_arrival();
|
||||
|
||||
// The oldest arrival should be close to t1, certainly before t2
|
||||
ASSERT_GE(oldest, t1);
|
||||
ASSERT_LT(oldest, t2);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. Stop and Signaling Tests
|
||||
// =================================================================
|
||||
|
||||
TEST(ThreadSafeBoundedQueueTest, StopUnblocksBlockedProducers) {
|
||||
thread_safe_bounded_queue<TestTask> queue(1);
|
||||
queue.push(TestTask(1)); // Fill it
|
||||
|
||||
std::atomic<bool> producer_exited{false};
|
||||
std::thread producer([&] {
|
||||
// This blocks because queue is full
|
||||
size_t result = queue.push(TestTask(2));
|
||||
// result should be 0 because the queue was stopped
|
||||
if (result == 0) {
|
||||
producer_exited = true;
|
||||
}
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
queue.stop(); // This should wake the producer up
|
||||
|
||||
producer.join();
|
||||
ASSERT_TRUE(producer_exited.load());
|
||||
}
|
||||
|
||||
TEST(ThreadSafeBoundedQueueTest, StopReturnsNullOptToConsumers) {
|
||||
thread_safe_bounded_queue<TestTask> queue(5);
|
||||
|
||||
std::thread consumer([&] {
|
||||
auto result = queue.try_pop(1s);
|
||||
ASSERT_FALSE(result.has_value());
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(20ms);
|
||||
queue.stop();
|
||||
consumer.join();
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. Stress Tests (Concurrent Producers and Consumers)
|
||||
// =================================================================
|
||||
|
||||
TEST(ThreadSafeBoundedQueueTest, HighContentionStressTest) {
|
||||
const int num_producers = 4;
|
||||
const int num_consumers = 4;
|
||||
const int items_per_producer = 1000;
|
||||
const size_t queue_limit = 10;
|
||||
|
||||
thread_safe_bounded_queue<TestTask> queue(queue_limit);
|
||||
std::atomic<int> total_popped{0};
|
||||
std::atomic<int> sum_popped{0};
|
||||
|
||||
std::vector<std::thread> workers;
|
||||
|
||||
// Consumers
|
||||
for (int i = 0; i < num_consumers; ++i) {
|
||||
workers.emplace_back([&] {
|
||||
while (total_popped < (num_producers * items_per_producer)) {
|
||||
auto val = queue.try_pop(10ms);
|
||||
if (val) {
|
||||
sum_popped += val->value;
|
||||
total_popped++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Producers
|
||||
for (int i = 0; i < num_producers; ++i) {
|
||||
workers.emplace_back([&] {
|
||||
for (int j = 0; j < items_per_producer; ++j) {
|
||||
queue.push(TestTask(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& w : workers)
|
||||
w.join();
|
||||
|
||||
ASSERT_EQ(total_popped.load(), num_producers * items_per_producer);
|
||||
ASSERT_EQ(sum_popped.load(), num_producers * items_per_producer);
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <everest/util/queue/thread_safe_queue.hpp>
|
||||
#include <memory> // For std::unique_ptr
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <type_traits> // For std::is_same_v
|
||||
#include <vector>
|
||||
|
||||
// Note: Add includes for your simple_queue and thread_safe_queue here
|
||||
// #include "simple_queue.h"
|
||||
// #include "thread_safe_queue.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace everest::lib::util;
|
||||
|
||||
// =================================================================
|
||||
// 1. Test Fixture Setup
|
||||
// =================================================================
|
||||
|
||||
// Helper to initialize TypeParam correctly in typed tests
|
||||
template <typename T> T initialize_value(int id) {
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
return id;
|
||||
} else if constexpr (std::is_same_v<T, std::string>) {
|
||||
return "Value_" + std::to_string(id);
|
||||
} else {
|
||||
// Fallback for other non-tested types
|
||||
return T{};
|
||||
}
|
||||
}
|
||||
|
||||
// Test Fixture
|
||||
template <typename T> class ThreadSafeQueueTest : public ::testing::Test {
|
||||
protected:
|
||||
thread_safe_queue<T> queue;
|
||||
};
|
||||
|
||||
// Typed Test Suite for int and std::string
|
||||
using QueueTypes = ::testing::Types<int, std::string>;
|
||||
TYPED_TEST_SUITE(ThreadSafeQueueTest, QueueTypes);
|
||||
|
||||
// Define a test suite specifically for concurrency checks (using int)
|
||||
using ThreadSafeQueueIntTest = ThreadSafeQueueTest<int>;
|
||||
|
||||
// =================================================================
|
||||
// 2. Basic Functionality Tests (Single Thread)
|
||||
// =================================================================
|
||||
|
||||
TYPED_TEST(ThreadSafeQueueTest, PushAndPopSimple) {
|
||||
TypeParam expected_value = initialize_value<TypeParam>(42);
|
||||
|
||||
this->queue.push(expected_value);
|
||||
|
||||
// Test the non-blocking pop
|
||||
std::optional<TypeParam> result = this->queue.try_pop();
|
||||
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(result.value(), expected_value);
|
||||
ASSERT_FALSE(this->queue.try_pop().has_value());
|
||||
}
|
||||
|
||||
TYPED_TEST(ThreadSafeQueueTest, MultiplePushAndPopOrder) {
|
||||
const int count = 5;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
this->queue.push(initialize_value<TypeParam>(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
TypeParam expected_value = initialize_value<TypeParam>(i);
|
||||
std::optional<TypeParam> result = this->queue.try_pop();
|
||||
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(result.value(), expected_value);
|
||||
}
|
||||
ASSERT_FALSE(this->queue.try_pop().has_value());
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. Time-Based Functionality Tests
|
||||
// =================================================================
|
||||
|
||||
TYPED_TEST(ThreadSafeQueueTest, TryPopWithTimeout_Timeout) {
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
// Try to pop with a short timeout
|
||||
std::optional<TypeParam> result = this->queue.try_pop(10ms);
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
ASSERT_FALSE(result.has_value());
|
||||
|
||||
auto elapsed = end - start;
|
||||
ASSERT_GE(elapsed, 9ms);
|
||||
ASSERT_LE(elapsed, 50ms);
|
||||
}
|
||||
|
||||
TYPED_TEST(ThreadSafeQueueTest, TryPopWithTimeout_ImmediateSuccess) {
|
||||
TypeParam value = initialize_value<TypeParam>(101);
|
||||
this->queue.push(value);
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
std::optional<TypeParam> result = this->queue.try_pop(10s);
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
ASSERT_TRUE(result.has_value());
|
||||
ASSERT_EQ(result.value(), value);
|
||||
|
||||
ASSERT_LT(end - start, 5ms);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. Synchronization and Blocking Tests
|
||||
// =================================================================
|
||||
|
||||
TEST_F(ThreadSafeQueueIntTest, BlockingPopUnblocksOnPush) {
|
||||
const int expected_value = 123;
|
||||
std::atomic<int> result = 0;
|
||||
|
||||
std::thread consumer([this, &result] {
|
||||
// Blocking pop() call
|
||||
result = this->queue.pop();
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
this->queue.push(expected_value);
|
||||
|
||||
consumer.join();
|
||||
ASSERT_EQ(result.load(), expected_value);
|
||||
}
|
||||
|
||||
TEST_F(ThreadSafeQueueIntTest, MultipleWaitersUnblockedSequentially) {
|
||||
const int num_waiters = 5;
|
||||
std::vector<std::thread> consumers;
|
||||
std::atomic<int> pops_received = 0;
|
||||
|
||||
for (int i = 0; i < num_waiters; ++i) {
|
||||
consumers.emplace_back([this, &pops_received] {
|
||||
this->queue.pop();
|
||||
pops_received++;
|
||||
});
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
// Push exactly the number of waiters—only one waiter should be released per push
|
||||
for (int i = 0; i < num_waiters; ++i) {
|
||||
this->queue.push(i);
|
||||
}
|
||||
|
||||
for (auto& t : consumers) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
ASSERT_EQ(pops_received.load(), num_waiters);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 5. Stress and Race Condition Tests
|
||||
// =================================================================
|
||||
|
||||
TEST_F(ThreadSafeQueueIntTest, ConcurrentPushConsistency) {
|
||||
const int num_producers = 10;
|
||||
const int items_per_producer = 1000;
|
||||
const int total_items = num_producers * items_per_producer;
|
||||
|
||||
std::vector<std::thread> producers;
|
||||
std::set<int> expected_values;
|
||||
|
||||
for (int i = 0; i < num_producers; ++i) {
|
||||
producers.emplace_back([this, i, items_per_producer] {
|
||||
int start_value = i * items_per_producer;
|
||||
for (int j = 0; j < items_per_producer; ++j) {
|
||||
this->queue.push(start_value + j);
|
||||
}
|
||||
});
|
||||
int start_value = i * items_per_producer;
|
||||
for (int j = 0; j < items_per_producer; ++j) {
|
||||
expected_values.insert(start_value + j);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& t : producers) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Drain the queue and check for consistency
|
||||
std::set<int> retrieved_values;
|
||||
for (int i = 0; i < total_items; ++i) {
|
||||
auto val = this->queue.pop();
|
||||
retrieved_values.insert(val);
|
||||
}
|
||||
|
||||
ASSERT_EQ(retrieved_values.size(), total_items);
|
||||
ASSERT_EQ(retrieved_values, expected_values);
|
||||
}
|
||||
|
||||
TEST_F(ThreadSafeQueueIntTest, ConcurrentPopNoDuplicate) {
|
||||
const int total_items = 10000;
|
||||
const int num_consumers = 10;
|
||||
|
||||
// Producer pushes all items
|
||||
for (int i = 0; i < total_items; ++i) {
|
||||
this->queue.push(i);
|
||||
}
|
||||
|
||||
// Consumers pop concurrently
|
||||
std::vector<std::thread> consumers;
|
||||
std::mutex result_mtx;
|
||||
std::set<int> retrieved_values;
|
||||
std::atomic<int> pop_count = 0;
|
||||
|
||||
for (int i = 0; i < num_consumers; ++i) {
|
||||
consumers.emplace_back([this, &result_mtx, &retrieved_values, &pop_count, total_items] {
|
||||
while (pop_count.load() < total_items) {
|
||||
// Use try_pop so threads don't block indefinitely waiting for a push
|
||||
// that won't come until the other threads finish.
|
||||
if (auto val = this->queue.try_pop(); val.has_value()) {
|
||||
std::lock_guard lock(result_mtx);
|
||||
retrieved_values.insert(val.value());
|
||||
pop_count++;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : consumers) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Check consistency
|
||||
ASSERT_EQ(pop_count.load(), total_items) << "Total pops do not match total pushed items.";
|
||||
ASSERT_EQ(retrieved_values.size(), total_items) << "Duplicate items were retrieved.";
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 6. Move-Only Type Compatibility Test (Verifying the push/pop fix)
|
||||
// =================================================================
|
||||
|
||||
// Test fixture for std::unique_ptr<int> (a move-only type)
|
||||
class ThreadSafeQueueMoveOnlyTest : public ::testing::Test {
|
||||
protected:
|
||||
thread_safe_queue<std::unique_ptr<int>> queue;
|
||||
};
|
||||
|
||||
TEST_F(ThreadSafeQueueMoveOnlyTest, HandlesConcurrentMoveOnlyTypes) {
|
||||
const int total_items = 1000;
|
||||
const int num_threads = 5;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic<int> pop_count = 0;
|
||||
|
||||
// Producer/Consumer set for unique ownership verification
|
||||
std::set<int> retrieved_values;
|
||||
std::mutex result_mtx;
|
||||
|
||||
// Start 5 threads: 3 producers, 2 consumers
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
if (i < 3) { // Producers
|
||||
threads.emplace_back([this, i, total_items] {
|
||||
int start_value = i * total_items;
|
||||
for (int j = 0; j < total_items; ++j) {
|
||||
// Requires thread_safe_queue::push(T&&)
|
||||
this->queue.push(std::make_unique<int>(start_value + j));
|
||||
}
|
||||
});
|
||||
} else { // Consumers
|
||||
threads.emplace_back([this, &pop_count, &result_mtx, &retrieved_values, total_items] {
|
||||
int pops = 0;
|
||||
while (pops < total_items * 3 / 2) { // Try to pop 1500 times
|
||||
// Requires thread_safe_queue::try_pop()
|
||||
if (auto opt_ptr = this->queue.try_pop(); opt_ptr.has_value()) {
|
||||
std::lock_guard lock(result_mtx);
|
||||
retrieved_values.insert(*opt_ptr.value());
|
||||
pop_count++;
|
||||
pops++;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
if (t.joinable()) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
// Drain any remaining items in the main thread (should be few/none)
|
||||
while (auto opt_ptr = this->queue.try_pop()) {
|
||||
std::lock_guard lock(result_mtx);
|
||||
retrieved_values.insert(*opt_ptr.value());
|
||||
pop_count++;
|
||||
}
|
||||
|
||||
const int total_expected = total_items * 3; // 3 producers * 1000 items
|
||||
|
||||
ASSERT_EQ(pop_count.load(), total_expected) << "Total items popped does not match total pushed.";
|
||||
ASSERT_EQ(retrieved_values.size(), total_expected)
|
||||
<< "Duplicate pointers/values were retrieved, indicating a race condition failure or a failed move.";
|
||||
}
|
||||
Reference in New Issue
Block a user