style(test): change indentation from 4 spaces to 2 spaces

- Update all test files to use 2-space indentation
- Affects: test_buffer.cpp, test_shm.cpp, test_mutex.cpp
- Affects: test_semaphore.cpp, test_condition.cpp
- Affects: test_locks.cpp, test_ipc_channel.cpp
- Improves code consistency and readability
This commit is contained in:
木头云 2025-11-30 04:22:24 +00:00
parent b5146655fa
commit 2cde78d692
7 changed files with 2633 additions and 2633 deletions

View File

@ -22,12 +22,12 @@ namespace {
// Custom destructor tracker for testing // Custom destructor tracker for testing
struct DestructorTracker { struct DestructorTracker {
static int count; static int count;
static void reset() { count = 0; } static void reset() { count = 0; }
static void destructor(void* p, std::size_t) { static void destructor(void* p, std::size_t) {
++count; ++count;
delete[] static_cast<char*>(p); delete[] static_cast<char*>(p);
} }
}; };
int DestructorTracker::count = 0; int DestructorTracker::count = 0;
@ -35,334 +35,334 @@ int DestructorTracker::count = 0;
class BufferTest : public ::testing::Test { class BufferTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
DestructorTracker::reset(); DestructorTracker::reset();
} }
}; };
// Test default constructor // Test default constructor
TEST_F(BufferTest, DefaultConstructor) { TEST_F(BufferTest, DefaultConstructor) {
buffer buf; buffer buf;
EXPECT_TRUE(buf.empty()); EXPECT_TRUE(buf.empty());
EXPECT_EQ(buf.size(), 0u); EXPECT_EQ(buf.size(), 0u);
EXPECT_EQ(buf.data(), nullptr); EXPECT_EQ(buf.data(), nullptr);
} }
// Test constructor with pointer, size, and destructor // Test constructor with pointer, size, and destructor
TEST_F(BufferTest, ConstructorWithDestructor) { TEST_F(BufferTest, ConstructorWithDestructor) {
const char* test_data = "Hello, World!"; const char* test_data = "Hello, World!";
std::size_t size = std::strlen(test_data) + 1; std::size_t size = std::strlen(test_data) + 1;
char* data = new char[size]; char* data = new char[size];
std::strcpy(data, test_data); std::strcpy(data, test_data);
buffer buf(data, size, DestructorTracker::destructor); buffer buf(data, size, DestructorTracker::destructor);
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), size); EXPECT_EQ(buf.size(), size);
EXPECT_NE(buf.data(), nullptr); EXPECT_NE(buf.data(), nullptr);
EXPECT_STREQ(static_cast<const char*>(buf.data()), test_data); EXPECT_STREQ(static_cast<const char*>(buf.data()), test_data);
} }
// Test destructor is called // Test destructor is called
TEST_F(BufferTest, DestructorCalled) { TEST_F(BufferTest, DestructorCalled) {
{ {
char* data = new char[100]; char* data = new char[100];
buffer buf(data, 100, DestructorTracker::destructor); buffer buf(data, 100, DestructorTracker::destructor);
EXPECT_EQ(DestructorTracker::count, 0); EXPECT_EQ(DestructorTracker::count, 0);
} }
EXPECT_EQ(DestructorTracker::count, 1); EXPECT_EQ(DestructorTracker::count, 1);
} }
// Test constructor with additional parameter // Test constructor with additional parameter
TEST_F(BufferTest, ConstructorWithAdditional) { TEST_F(BufferTest, ConstructorWithAdditional) {
char* data = new char[50]; char* data = new char[50];
int additional_value = 42; int additional_value = 42;
buffer buf(data, 50, DestructorTracker::destructor, &additional_value); buffer buf(data, 50, DestructorTracker::destructor, &additional_value);
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 50u); EXPECT_EQ(buf.size(), 50u);
EXPECT_NE(buf.data(), nullptr); EXPECT_NE(buf.data(), nullptr);
} }
// Test constructor without destructor // Test constructor without destructor
TEST_F(BufferTest, ConstructorWithoutDestructor) { TEST_F(BufferTest, ConstructorWithoutDestructor) {
char stack_data[20] = "Stack data"; char stack_data[20] = "Stack data";
buffer buf(stack_data, 20); buffer buf(stack_data, 20);
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 20u); EXPECT_EQ(buf.size(), 20u);
EXPECT_EQ(buf.data(), stack_data); EXPECT_EQ(buf.data(), stack_data);
} }
// Test constructor from byte array // Test constructor from byte array
TEST_F(BufferTest, ConstructorFromByteArray) { TEST_F(BufferTest, ConstructorFromByteArray) {
byte_t data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; byte_t data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
buffer buf(data); buffer buf(data);
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 10u); EXPECT_EQ(buf.size(), 10u);
const byte_t* buf_data = buf.get<const byte_t*>(); const byte_t* buf_data = buf.get<const byte_t*>();
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
EXPECT_EQ(buf_data[i], i); EXPECT_EQ(buf_data[i], i);
} }
} }
// Test constructor from single char // Test constructor from single char
TEST_F(BufferTest, ConstructorFromChar) { TEST_F(BufferTest, ConstructorFromChar) {
char c = 'X'; char c = 'X';
buffer buf(c); buffer buf(c);
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), sizeof(char)); EXPECT_EQ(buf.size(), sizeof(char));
EXPECT_EQ(*buf.get<const char*>(), 'X'); EXPECT_EQ(*buf.get<const char*>(), 'X');
} }
// Test move constructor // Test move constructor
TEST_F(BufferTest, MoveConstructor) { TEST_F(BufferTest, MoveConstructor) {
char* data = new char[30]; char* data = new char[30];
std::strcpy(data, "Move test"); std::strcpy(data, "Move test");
buffer buf1(data, 30, DestructorTracker::destructor); buffer buf1(data, 30, DestructorTracker::destructor);
void* original_ptr = buf1.data(); void* original_ptr = buf1.data();
std::size_t original_size = buf1.size(); std::size_t original_size = buf1.size();
buffer buf2(std::move(buf1)); buffer buf2(std::move(buf1));
// buf2 should have the original data // buf2 should have the original data
EXPECT_EQ(buf2.data(), original_ptr); EXPECT_EQ(buf2.data(), original_ptr);
EXPECT_EQ(buf2.size(), original_size); EXPECT_EQ(buf2.size(), original_size);
EXPECT_FALSE(buf2.empty()); EXPECT_FALSE(buf2.empty());
// buf1 should be empty after move // buf1 should be empty after move
EXPECT_TRUE(buf1.empty()); EXPECT_TRUE(buf1.empty());
EXPECT_EQ(buf1.size(), 0u); EXPECT_EQ(buf1.size(), 0u);
} }
// Test swap // Test swap
TEST_F(BufferTest, Swap) { TEST_F(BufferTest, Swap) {
char* data1 = new char[20]; char* data1 = new char[20];
char* data2 = new char[30]; char* data2 = new char[30];
std::strcpy(data1, "Buffer 1"); std::strcpy(data1, "Buffer 1");
std::strcpy(data2, "Buffer 2"); std::strcpy(data2, "Buffer 2");
buffer buf1(data1, 20, DestructorTracker::destructor); buffer buf1(data1, 20, DestructorTracker::destructor);
buffer buf2(data2, 30, DestructorTracker::destructor); buffer buf2(data2, 30, DestructorTracker::destructor);
void* ptr1 = buf1.data(); void* ptr1 = buf1.data();
void* ptr2 = buf2.data(); void* ptr2 = buf2.data();
std::size_t size1 = buf1.size(); std::size_t size1 = buf1.size();
std::size_t size2 = buf2.size(); std::size_t size2 = buf2.size();
buf1.swap(buf2); buf1.swap(buf2);
EXPECT_EQ(buf1.data(), ptr2); EXPECT_EQ(buf1.data(), ptr2);
EXPECT_EQ(buf1.size(), size2); EXPECT_EQ(buf1.size(), size2);
EXPECT_EQ(buf2.data(), ptr1); EXPECT_EQ(buf2.data(), ptr1);
EXPECT_EQ(buf2.size(), size1); EXPECT_EQ(buf2.size(), size1);
} }
// Test assignment operator (move semantics) // Test assignment operator (move semantics)
TEST_F(BufferTest, AssignmentOperator) { TEST_F(BufferTest, AssignmentOperator) {
char* data = new char[40]; char* data = new char[40];
std::strcpy(data, "Assignment test"); std::strcpy(data, "Assignment test");
buffer buf1(data, 40, DestructorTracker::destructor); buffer buf1(data, 40, DestructorTracker::destructor);
void* original_ptr = buf1.data(); void* original_ptr = buf1.data();
buffer buf2; buffer buf2;
buf2 = std::move(buf1); buf2 = std::move(buf1);
EXPECT_EQ(buf2.data(), original_ptr); EXPECT_EQ(buf2.data(), original_ptr);
EXPECT_FALSE(buf2.empty()); EXPECT_FALSE(buf2.empty());
} }
// Test empty() method // Test empty() method
TEST_F(BufferTest, EmptyMethod) { TEST_F(BufferTest, EmptyMethod) {
buffer buf1; buffer buf1;
EXPECT_TRUE(buf1.empty()); EXPECT_TRUE(buf1.empty());
char* data = new char[10]; char* data = new char[10];
buffer buf2(data, 10, DestructorTracker::destructor); buffer buf2(data, 10, DestructorTracker::destructor);
EXPECT_FALSE(buf2.empty()); EXPECT_FALSE(buf2.empty());
} }
// Test data() const method // Test data() const method
TEST_F(BufferTest, DataConstMethod) { TEST_F(BufferTest, DataConstMethod) {
const char* test_str = "Const data test"; const char* test_str = "Const data test";
std::size_t size = std::strlen(test_str) + 1; std::size_t size = std::strlen(test_str) + 1;
char* data = new char[size]; char* data = new char[size];
std::strcpy(data, test_str); std::strcpy(data, test_str);
const buffer buf(data, size, DestructorTracker::destructor); const buffer buf(data, size, DestructorTracker::destructor);
const void* const_data = buf.data(); const void* const_data = buf.data();
EXPECT_NE(const_data, nullptr); EXPECT_NE(const_data, nullptr);
EXPECT_STREQ(static_cast<const char*>(const_data), test_str); EXPECT_STREQ(static_cast<const char*>(const_data), test_str);
} }
// Test get<T>() template method // Test get<T>() template method
TEST_F(BufferTest, GetTemplateMethod) { TEST_F(BufferTest, GetTemplateMethod) {
int* int_data = new int[5]{1, 2, 3, 4, 5}; int* int_data = new int[5]{1, 2, 3, 4, 5};
buffer buf(int_data, 5 * sizeof(int), [](void* p, std::size_t) { buffer buf(int_data, 5 * sizeof(int), [](void* p, std::size_t) {
delete[] static_cast<int*>(p); delete[] static_cast<int*>(p);
}); });
int* retrieved = buf.get<int*>(); int* retrieved = buf.get<int*>();
EXPECT_NE(retrieved, nullptr); EXPECT_NE(retrieved, nullptr);
EXPECT_EQ(retrieved[0], 1); EXPECT_EQ(retrieved[0], 1);
EXPECT_EQ(retrieved[4], 5); EXPECT_EQ(retrieved[4], 5);
} }
// Test to_tuple() non-const version // Test to_tuple() non-const version
TEST_F(BufferTest, ToTupleNonConst) { TEST_F(BufferTest, ToTupleNonConst) {
char* data = new char[25]; char* data = new char[25];
std::strcpy(data, "Tuple test"); std::strcpy(data, "Tuple test");
buffer buf(data, 25, DestructorTracker::destructor); buffer buf(data, 25, DestructorTracker::destructor);
auto [ptr, size] = buf.to_tuple(); auto [ptr, size] = buf.to_tuple();
EXPECT_EQ(ptr, buf.data()); EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size()); EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 25u); EXPECT_EQ(size, 25u);
} }
// Test to_tuple() const version // Test to_tuple() const version
TEST_F(BufferTest, ToTupleConst) { TEST_F(BufferTest, ToTupleConst) {
char* data = new char[30]; char* data = new char[30];
std::strcpy(data, "Const tuple"); std::strcpy(data, "Const tuple");
const buffer buf(data, 30, DestructorTracker::destructor); const buffer buf(data, 30, DestructorTracker::destructor);
auto [ptr, size] = buf.to_tuple(); auto [ptr, size] = buf.to_tuple();
EXPECT_EQ(ptr, buf.data()); EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size()); EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 30u); EXPECT_EQ(size, 30u);
} }
// Test to_vector() method // Test to_vector() method
TEST_F(BufferTest, ToVector) { TEST_F(BufferTest, ToVector) {
byte_t data_arr[5] = {10, 20, 30, 40, 50}; byte_t data_arr[5] = {10, 20, 30, 40, 50};
buffer buf(data_arr, 5); buffer buf(data_arr, 5);
std::vector<byte_t> vec = buf.to_vector(); std::vector<byte_t> vec = buf.to_vector();
ASSERT_EQ(vec.size(), 5u); ASSERT_EQ(vec.size(), 5u);
EXPECT_EQ(vec[0], 10); EXPECT_EQ(vec[0], 10);
EXPECT_EQ(vec[1], 20); EXPECT_EQ(vec[1], 20);
EXPECT_EQ(vec[2], 30); EXPECT_EQ(vec[2], 30);
EXPECT_EQ(vec[3], 40); EXPECT_EQ(vec[3], 40);
EXPECT_EQ(vec[4], 50); EXPECT_EQ(vec[4], 50);
} }
// Test equality operator // Test equality operator
TEST_F(BufferTest, EqualityOperator) { TEST_F(BufferTest, EqualityOperator) {
byte_t data1[5] = {1, 2, 3, 4, 5}; byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[5] = {1, 2, 3, 4, 5}; byte_t data2[5] = {1, 2, 3, 4, 5};
byte_t data3[5] = {5, 4, 3, 2, 1}; byte_t data3[5] = {5, 4, 3, 2, 1};
buffer buf1(data1, 5); buffer buf1(data1, 5);
buffer buf2(data2, 5); buffer buf2(data2, 5);
buffer buf3(data3, 5); buffer buf3(data3, 5);
EXPECT_TRUE(buf1 == buf2); EXPECT_TRUE(buf1 == buf2);
EXPECT_FALSE(buf1 == buf3); EXPECT_FALSE(buf1 == buf3);
} }
// Test inequality operator // Test inequality operator
TEST_F(BufferTest, InequalityOperator) { TEST_F(BufferTest, InequalityOperator) {
byte_t data1[5] = {1, 2, 3, 4, 5}; byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[5] = {1, 2, 3, 4, 5}; byte_t data2[5] = {1, 2, 3, 4, 5};
byte_t data3[5] = {5, 4, 3, 2, 1}; byte_t data3[5] = {5, 4, 3, 2, 1};
buffer buf1(data1, 5); buffer buf1(data1, 5);
buffer buf2(data2, 5); buffer buf2(data2, 5);
buffer buf3(data3, 5); buffer buf3(data3, 5);
EXPECT_FALSE(buf1 != buf2); EXPECT_FALSE(buf1 != buf2);
EXPECT_TRUE(buf1 != buf3); EXPECT_TRUE(buf1 != buf3);
} }
// Test size mismatch in equality // Test size mismatch in equality
TEST_F(BufferTest, EqualityWithDifferentSizes) { TEST_F(BufferTest, EqualityWithDifferentSizes) {
byte_t data1[5] = {1, 2, 3, 4, 5}; byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[3] = {1, 2, 3}; byte_t data2[3] = {1, 2, 3};
buffer buf1(data1, 5); buffer buf1(data1, 5);
buffer buf2(data2, 3); buffer buf2(data2, 3);
EXPECT_FALSE(buf1 == buf2); EXPECT_FALSE(buf1 == buf2);
EXPECT_TRUE(buf1 != buf2); EXPECT_TRUE(buf1 != buf2);
} }
// Test empty buffers comparison // Test empty buffers comparison
TEST_F(BufferTest, EmptyBuffersComparison) { TEST_F(BufferTest, EmptyBuffersComparison) {
buffer buf1; buffer buf1;
buffer buf2; buffer buf2;
EXPECT_TRUE(buf1 == buf2); EXPECT_TRUE(buf1 == buf2);
EXPECT_FALSE(buf1 != buf2); EXPECT_FALSE(buf1 != buf2);
} }
// Test large buffer // Test large buffer
TEST_F(BufferTest, LargeBuffer) { TEST_F(BufferTest, LargeBuffer) {
const std::size_t large_size = 1024 * 1024; // 1MB const std::size_t large_size = 1024 * 1024; // 1MB
char* large_data = new char[large_size]; char* large_data = new char[large_size];
// Fill with pattern // Fill with pattern
for (std::size_t i = 0; i < large_size; ++i) { for (std::size_t i = 0; i < large_size; ++i) {
large_data[i] = static_cast<char>(i % 256); large_data[i] = static_cast<char>(i % 256);
} }
buffer buf(large_data, large_size, [](void* p, std::size_t) { buffer buf(large_data, large_size, [](void* p, std::size_t) {
delete[] static_cast<char*>(p); delete[] static_cast<char*>(p);
}); });
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), large_size); EXPECT_EQ(buf.size(), large_size);
// Verify pattern // Verify pattern
const char* data_ptr = buf.get<const char*>(); const char* data_ptr = buf.get<const char*>();
for (std::size_t i = 0; i < 100; ++i) { // Check first 100 bytes for (std::size_t i = 0; i < 100; ++i) { // Check first 100 bytes
EXPECT_EQ(data_ptr[i], static_cast<char>(i % 256)); EXPECT_EQ(data_ptr[i], static_cast<char>(i % 256));
} }
} }
// Test multiple move operations // Test multiple move operations
TEST_F(BufferTest, MultipleMoves) { TEST_F(BufferTest, MultipleMoves) {
char* data = new char[15]; char* data = new char[15];
std::strcpy(data, "Multi-move"); std::strcpy(data, "Multi-move");
void* original_ptr = data; void* original_ptr = data;
buffer buf1(data, 15, DestructorTracker::destructor); buffer buf1(data, 15, DestructorTracker::destructor);
buffer buf2(std::move(buf1)); buffer buf2(std::move(buf1));
buffer buf3(std::move(buf2)); buffer buf3(std::move(buf2));
buffer buf4(std::move(buf3)); buffer buf4(std::move(buf3));
EXPECT_EQ(buf4.data(), original_ptr); EXPECT_EQ(buf4.data(), original_ptr);
EXPECT_TRUE(buf1.empty()); EXPECT_TRUE(buf1.empty());
EXPECT_TRUE(buf2.empty()); EXPECT_TRUE(buf2.empty());
EXPECT_TRUE(buf3.empty()); EXPECT_TRUE(buf3.empty());
EXPECT_FALSE(buf4.empty()); EXPECT_FALSE(buf4.empty());
} }
// Test self-assignment safety // Test self-assignment safety
TEST_F(BufferTest, SelfAssignment) { TEST_F(BufferTest, SelfAssignment) {
char* data = new char[20]; char* data = new char[20];
std::strcpy(data, "Self-assign"); std::strcpy(data, "Self-assign");
buffer buf(data, 20, DestructorTracker::destructor); buffer buf(data, 20, DestructorTracker::destructor);
void* original_ptr = buf.data(); void* original_ptr = buf.data();
std::size_t original_size = buf.size(); std::size_t original_size = buf.size();
buf = std::move(buf); // Self-assignment buf = std::move(buf); // Self-assignment
// Should remain valid // Should remain valid
EXPECT_EQ(buf.data(), original_ptr); EXPECT_EQ(buf.data(), original_ptr);
EXPECT_EQ(buf.size(), original_size); EXPECT_EQ(buf.size(), original_size);
} }

View File

@ -26,525 +26,525 @@ using namespace ipc::sync;
namespace { namespace {
std::string generate_unique_cv_name(const char* prefix) { std::string generate_unique_cv_name(const char* prefix) {
static int counter = 0; static int counter = 0;
return std::string(prefix) + "_cv_" + std::to_string(++counter); return std::string(prefix) + "_cv_" + std::to_string(++counter);
} }
} // anonymous namespace } // anonymous namespace
class ConditionTest : public ::testing::Test { class ConditionTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}; };
// Test default constructor // Test default constructor
TEST_F(ConditionTest, DefaultConstructor) { TEST_F(ConditionTest, DefaultConstructor) {
condition cv; condition cv;
} }
// Test named constructor // Test named constructor
TEST_F(ConditionTest, NamedConstructor) { TEST_F(ConditionTest, NamedConstructor) {
std::string name = generate_unique_cv_name("named"); std::string name = generate_unique_cv_name("named");
condition cv(name.c_str()); condition cv(name.c_str());
EXPECT_TRUE(cv.valid()); EXPECT_TRUE(cv.valid());
} }
// Test native() methods // Test native() methods
TEST_F(ConditionTest, NativeHandle) { TEST_F(ConditionTest, NativeHandle) {
std::string name = generate_unique_cv_name("native"); std::string name = generate_unique_cv_name("native");
condition cv(name.c_str()); condition cv(name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
const void* const_handle = static_cast<const condition&>(cv).native(); const void* const_handle = static_cast<const condition&>(cv).native();
void* handle = cv.native(); void* handle = cv.native();
EXPECT_NE(const_handle, nullptr); EXPECT_NE(const_handle, nullptr);
EXPECT_NE(handle, nullptr); EXPECT_NE(handle, nullptr);
} }
// Test valid() method // Test valid() method
TEST_F(ConditionTest, Valid) { TEST_F(ConditionTest, Valid) {
condition cv1; condition cv1;
std::string name = generate_unique_cv_name("valid"); std::string name = generate_unique_cv_name("valid");
condition cv2(name.c_str()); condition cv2(name.c_str());
EXPECT_TRUE(cv2.valid()); EXPECT_TRUE(cv2.valid());
} }
// Test open() method // Test open() method
TEST_F(ConditionTest, Open) { TEST_F(ConditionTest, Open) {
std::string name = generate_unique_cv_name("open"); std::string name = generate_unique_cv_name("open");
condition cv; condition cv;
bool result = cv.open(name.c_str()); bool result = cv.open(name.c_str());
EXPECT_TRUE(result); EXPECT_TRUE(result);
EXPECT_TRUE(cv.valid()); EXPECT_TRUE(cv.valid());
} }
// Test close() method // Test close() method
TEST_F(ConditionTest, Close) { TEST_F(ConditionTest, Close) {
std::string name = generate_unique_cv_name("close"); std::string name = generate_unique_cv_name("close");
condition cv(name.c_str()); condition cv(name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
cv.close(); cv.close();
EXPECT_FALSE(cv.valid()); EXPECT_FALSE(cv.valid());
} }
// Test clear() method // Test clear() method
TEST_F(ConditionTest, Clear) { TEST_F(ConditionTest, Clear) {
std::string name = generate_unique_cv_name("clear"); std::string name = generate_unique_cv_name("clear");
condition cv(name.c_str()); condition cv(name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
cv.clear(); cv.clear();
EXPECT_FALSE(cv.valid()); EXPECT_FALSE(cv.valid());
} }
// Test clear_storage() static method // Test clear_storage() static method
TEST_F(ConditionTest, ClearStorage) { TEST_F(ConditionTest, ClearStorage) {
std::string name = generate_unique_cv_name("clear_storage"); std::string name = generate_unique_cv_name("clear_storage");
{ {
condition cv(name.c_str()); condition cv(name.c_str());
EXPECT_TRUE(cv.valid()); EXPECT_TRUE(cv.valid());
} }
condition::clear_storage(name.c_str()); condition::clear_storage(name.c_str());
} }
// Test basic wait and notify // Test basic wait and notify
TEST_F(ConditionTest, WaitNotify) { TEST_F(ConditionTest, WaitNotify) {
std::string cv_name = generate_unique_cv_name("wait_notify"); std::string cv_name = generate_unique_cv_name("wait_notify");
std::string mtx_name = generate_unique_cv_name("wait_notify_mtx"); std::string mtx_name = generate_unique_cv_name("wait_notify_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<bool> notified{false}; std::atomic<bool> notified{false};
std::thread waiter([&]() { std::thread waiter([&]() {
mtx.lock(); mtx.lock();
cv.wait(mtx); cv.wait(mtx);
notified.store(true); notified.store(true);
mtx.unlock(); mtx.unlock();
}); });
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock(); mtx.lock();
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
waiter.join(); waiter.join();
EXPECT_TRUE(notified.load()); EXPECT_TRUE(notified.load());
} }
// Test broadcast to multiple waiters // Test broadcast to multiple waiters
TEST_F(ConditionTest, Broadcast) { TEST_F(ConditionTest, Broadcast) {
std::string cv_name = generate_unique_cv_name("broadcast"); std::string cv_name = generate_unique_cv_name("broadcast");
std::string mtx_name = generate_unique_cv_name("broadcast_mtx"); std::string mtx_name = generate_unique_cv_name("broadcast_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<int> notified_count{0}; std::atomic<int> notified_count{0};
const int num_waiters = 5; const int num_waiters = 5;
std::vector<std::thread> waiters; std::vector<std::thread> waiters;
for (int i = 0; i < num_waiters; ++i) { for (int i = 0; i < num_waiters; ++i) {
waiters.emplace_back([&]() { waiters.emplace_back([&]() {
mtx.lock(); mtx.lock();
cv.wait(mtx); cv.wait(mtx);
++notified_count; ++notified_count;
mtx.unlock(); mtx.unlock();
}); });
} }
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock(); mtx.lock();
cv.broadcast(mtx); cv.broadcast(mtx);
mtx.unlock(); mtx.unlock();
for (auto& t : waiters) { for (auto& t : waiters) {
t.join(); t.join();
} }
EXPECT_EQ(notified_count.load(), num_waiters); EXPECT_EQ(notified_count.load(), num_waiters);
} }
// Test timed wait with timeout // Test timed wait with timeout
TEST_F(ConditionTest, TimedWait) { TEST_F(ConditionTest, TimedWait) {
std::string cv_name = generate_unique_cv_name("timed_wait"); std::string cv_name = generate_unique_cv_name("timed_wait");
std::string mtx_name = generate_unique_cv_name("timed_wait_mtx"); std::string mtx_name = generate_unique_cv_name("timed_wait_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
mtx.lock(); mtx.lock();
bool result = cv.wait(mtx, 100); // 100ms timeout bool result = cv.wait(mtx, 100); // 100ms timeout
mtx.unlock(); mtx.unlock();
auto end = std::chrono::steady_clock::now(); auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
EXPECT_FALSE(result); // Should timeout EXPECT_FALSE(result); // Should timeout
EXPECT_GE(elapsed, 80); // Allow some tolerance EXPECT_GE(elapsed, 80); // Allow some tolerance
} }
// Test wait with immediate notify // Test wait with immediate notify
TEST_F(ConditionTest, ImmediateNotify) { TEST_F(ConditionTest, ImmediateNotify) {
std::string cv_name = generate_unique_cv_name("immediate"); std::string cv_name = generate_unique_cv_name("immediate");
std::string mtx_name = generate_unique_cv_name("immediate_mtx"); std::string mtx_name = generate_unique_cv_name("immediate_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<bool> wait_started{false}; std::atomic<bool> wait_started{false};
std::atomic<bool> notified{false}; std::atomic<bool> notified{false};
std::thread waiter([&]() { std::thread waiter([&]() {
mtx.lock(); mtx.lock();
wait_started.store(true); wait_started.store(true);
cv.wait(mtx, 1000); // 1 second timeout cv.wait(mtx, 1000); // 1 second timeout
notified.store(true); notified.store(true);
mtx.unlock(); mtx.unlock();
}); });
// Wait for waiter to start // Wait for waiter to start
while (!wait_started.load()) { while (!wait_started.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
mtx.lock(); mtx.lock();
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
waiter.join(); waiter.join();
EXPECT_TRUE(notified.load()); EXPECT_TRUE(notified.load());
} }
// Test producer-consumer with condition variable // Test producer-consumer with condition variable
TEST_F(ConditionTest, ProducerConsumer) { TEST_F(ConditionTest, ProducerConsumer) {
std::string cv_name = generate_unique_cv_name("prod_cons"); std::string cv_name = generate_unique_cv_name("prod_cons");
std::string mtx_name = generate_unique_cv_name("prod_cons_mtx"); std::string mtx_name = generate_unique_cv_name("prod_cons_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<int> buffer{0}; std::atomic<int> buffer{0};
std::atomic<bool> ready{false}; std::atomic<bool> ready{false};
std::atomic<int> consumed_value{0}; std::atomic<int> consumed_value{0};
std::thread producer([&]() { std::thread producer([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock(); mtx.lock();
buffer.store(42); buffer.store(42);
ready.store(true); ready.store(true);
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
}); });
std::thread consumer([&]() { std::thread consumer([&]() {
mtx.lock(); mtx.lock();
while (!ready.load()) { while (!ready.load()) {
cv.wait(mtx, 2000); cv.wait(mtx, 2000);
} }
consumed_value.store(buffer.load()); consumed_value.store(buffer.load());
mtx.unlock(); mtx.unlock();
}); });
producer.join(); producer.join();
consumer.join(); consumer.join();
EXPECT_EQ(consumed_value.load(), 42); EXPECT_EQ(consumed_value.load(), 42);
} }
// Test multiple notify operations // Test multiple notify operations
TEST_F(ConditionTest, MultipleNotify) { TEST_F(ConditionTest, MultipleNotify) {
std::string cv_name = generate_unique_cv_name("multi_notify"); std::string cv_name = generate_unique_cv_name("multi_notify");
std::string mtx_name = generate_unique_cv_name("multi_notify_mtx"); std::string mtx_name = generate_unique_cv_name("multi_notify_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<int> notify_count{0}; std::atomic<int> notify_count{0};
const int num_notifications = 3; const int num_notifications = 3;
std::thread waiter([&]() { std::thread waiter([&]() {
for (int i = 0; i < num_notifications; ++i) { for (int i = 0; i < num_notifications; ++i) {
mtx.lock(); mtx.lock();
cv.wait(mtx, 1000); cv.wait(mtx, 1000);
++notify_count; ++notify_count;
mtx.unlock(); mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}); });
for (int i = 0; i < num_notifications; ++i) { for (int i = 0; i < num_notifications; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock(); mtx.lock();
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
} }
waiter.join(); waiter.join();
EXPECT_EQ(notify_count.load(), num_notifications); EXPECT_EQ(notify_count.load(), num_notifications);
} }
// Test notify vs broadcast // Test notify vs broadcast
TEST_F(ConditionTest, NotifyVsBroadcast) { TEST_F(ConditionTest, NotifyVsBroadcast) {
std::string cv_name = generate_unique_cv_name("notify_vs_broadcast"); std::string cv_name = generate_unique_cv_name("notify_vs_broadcast");
std::string mtx_name = generate_unique_cv_name("notify_vs_broadcast_mtx"); std::string mtx_name = generate_unique_cv_name("notify_vs_broadcast_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
// Test notify (should wake one) // Test notify (should wake one)
std::atomic<int> notify_woken{0}; std::atomic<int> notify_woken{0};
std::vector<std::thread> notify_waiters; std::vector<std::thread> notify_waiters;
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
notify_waiters.emplace_back([&]() { notify_waiters.emplace_back([&]() {
mtx.lock(); mtx.lock();
cv.wait(mtx, 100); cv.wait(mtx, 100);
++notify_woken; ++notify_woken;
mtx.unlock(); mtx.unlock();
}); });
} }
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock(); mtx.lock();
cv.notify(mtx); // Wake one cv.notify(mtx); // Wake one
mtx.unlock(); mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(150)); std::this_thread::sleep_for(std::chrono::milliseconds(150));
for (auto& t : notify_waiters) { for (auto& t : notify_waiters) {
t.join(); t.join();
} }
// At least one should be woken by notify // At least one should be woken by notify
EXPECT_GE(notify_woken.load(), 1); EXPECT_GE(notify_woken.load(), 1);
} }
// Test condition variable with spurious wakeups pattern // Test condition variable with spurious wakeups pattern
TEST_F(ConditionTest, SpuriousWakeupPattern) { TEST_F(ConditionTest, SpuriousWakeupPattern) {
std::string cv_name = generate_unique_cv_name("spurious"); std::string cv_name = generate_unique_cv_name("spurious");
std::string mtx_name = generate_unique_cv_name("spurious_mtx"); std::string mtx_name = generate_unique_cv_name("spurious_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<bool> predicate{false}; std::atomic<bool> predicate{false};
std::atomic<bool> done{false}; std::atomic<bool> done{false};
std::thread waiter([&]() { std::thread waiter([&]() {
mtx.lock(); mtx.lock();
while (!predicate.load()) { while (!predicate.load()) {
if (!cv.wait(mtx, 100)) { if (!cv.wait(mtx, 100)) {
// Timeout - check predicate again // Timeout - check predicate again
if (predicate.load()) break; if (predicate.load()) break;
} }
} }
done.store(true); done.store(true);
mtx.unlock(); mtx.unlock();
}); });
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock(); mtx.lock();
predicate.store(true); predicate.store(true);
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
waiter.join(); waiter.join();
EXPECT_TRUE(done.load()); EXPECT_TRUE(done.load());
} }
// Test reopen after close // Test reopen after close
TEST_F(ConditionTest, ReopenAfterClose) { TEST_F(ConditionTest, ReopenAfterClose) {
std::string name = generate_unique_cv_name("reopen"); std::string name = generate_unique_cv_name("reopen");
condition cv; condition cv;
ASSERT_TRUE(cv.open(name.c_str())); ASSERT_TRUE(cv.open(name.c_str()));
EXPECT_TRUE(cv.valid()); EXPECT_TRUE(cv.valid());
cv.close(); cv.close();
EXPECT_FALSE(cv.valid()); EXPECT_FALSE(cv.valid());
ASSERT_TRUE(cv.open(name.c_str())); ASSERT_TRUE(cv.open(name.c_str()));
EXPECT_TRUE(cv.valid()); EXPECT_TRUE(cv.valid());
} }
// Test named condition variable sharing between threads // Test named condition variable sharing between threads
TEST_F(ConditionTest, NamedSharing) { TEST_F(ConditionTest, NamedSharing) {
std::string cv_name = generate_unique_cv_name("sharing"); std::string cv_name = generate_unique_cv_name("sharing");
std::string mtx_name = generate_unique_cv_name("sharing_mtx"); std::string mtx_name = generate_unique_cv_name("sharing_mtx");
std::atomic<int> value{0}; std::atomic<int> value{0};
std::thread t1([&]() { std::thread t1([&]() {
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.lock(); mtx.lock();
cv.wait(mtx, 1000); cv.wait(mtx, 1000);
value.store(100); value.store(100);
mtx.unlock(); mtx.unlock();
}); });
std::thread t2([&]() { std::thread t2([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.lock(); mtx.lock();
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
}); });
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(value.load(), 100); EXPECT_EQ(value.load(), 100);
} }
// Test infinite wait // Test infinite wait
TEST_F(ConditionTest, InfiniteWait) { TEST_F(ConditionTest, InfiniteWait) {
std::string cv_name = generate_unique_cv_name("infinite"); std::string cv_name = generate_unique_cv_name("infinite");
std::string mtx_name = generate_unique_cv_name("infinite_mtx"); std::string mtx_name = generate_unique_cv_name("infinite_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<bool> woken{false}; std::atomic<bool> woken{false};
std::thread waiter([&]() { std::thread waiter([&]() {
mtx.lock(); mtx.lock();
cv.wait(mtx, invalid_value); // Infinite wait cv.wait(mtx, invalid_value); // Infinite wait
woken.store(true); woken.store(true);
mtx.unlock(); mtx.unlock();
}); });
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock(); mtx.lock();
cv.notify(mtx); cv.notify(mtx);
mtx.unlock(); mtx.unlock();
waiter.join(); waiter.join();
EXPECT_TRUE(woken.load()); EXPECT_TRUE(woken.load());
} }
// Test broadcast with sequential waiters // Test broadcast with sequential waiters
TEST_F(ConditionTest, BroadcastSequential) { TEST_F(ConditionTest, BroadcastSequential) {
std::string cv_name = generate_unique_cv_name("broadcast_seq"); std::string cv_name = generate_unique_cv_name("broadcast_seq");
std::string mtx_name = generate_unique_cv_name("broadcast_seq_mtx"); std::string mtx_name = generate_unique_cv_name("broadcast_seq_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<int> processed{0}; std::atomic<int> processed{0};
const int num_threads = 4; const int num_threads = 4;
std::vector<std::thread> threads; std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) { for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() { threads.emplace_back([&]() {
mtx.lock(); mtx.lock();
cv.wait(mtx, 2000); cv.wait(mtx, 2000);
++processed; ++processed;
mtx.unlock(); mtx.unlock();
}); });
} }
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock(); mtx.lock();
cv.broadcast(mtx); cv.broadcast(mtx);
mtx.unlock(); mtx.unlock();
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
EXPECT_EQ(processed.load(), num_threads); EXPECT_EQ(processed.load(), num_threads);
} }
// Test operations after clear // Test operations after clear
TEST_F(ConditionTest, AfterClear) { TEST_F(ConditionTest, AfterClear) {
std::string cv_name = generate_unique_cv_name("after_clear"); std::string cv_name = generate_unique_cv_name("after_clear");
std::string mtx_name = generate_unique_cv_name("after_clear_mtx"); std::string mtx_name = generate_unique_cv_name("after_clear_mtx");
condition cv(cv_name.c_str()); condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str()); mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid()); ASSERT_TRUE(cv.valid());
cv.clear(); cv.clear();
EXPECT_FALSE(cv.valid()); EXPECT_FALSE(cv.valid());
// Operations after clear should fail gracefully // Operations after clear should fail gracefully
mtx.lock(); mtx.lock();
EXPECT_FALSE(cv.wait(mtx, 10)); EXPECT_FALSE(cv.wait(mtx, 10));
EXPECT_FALSE(cv.notify(mtx)); EXPECT_FALSE(cv.notify(mtx));
EXPECT_FALSE(cv.broadcast(mtx)); EXPECT_FALSE(cv.broadcast(mtx));
mtx.unlock(); mtx.unlock();
} }

View File

@ -30,25 +30,25 @@ using namespace ipc;
namespace { namespace {
std::string generate_unique_ipc_name(const char* prefix) { std::string generate_unique_ipc_name(const char* prefix) {
static int counter = 0; static int counter = 0;
return std::string(prefix) + "_ipc_" + std::to_string(++counter); return std::string(prefix) + "_ipc_" + std::to_string(++counter);
} }
// Helper to create a test buffer with data // Helper to create a test buffer with data
buffer make_test_buffer(const std::string& data) { buffer make_test_buffer(const std::string& data) {
char* mem = new char[data.size() + 1]; char* mem = new char[data.size() + 1];
std::strcpy(mem, data.c_str()); std::strcpy(mem, data.c_str());
return buffer(mem, data.size() + 1, [](void* p, std::size_t) { return buffer(mem, data.size() + 1, [](void* p, std::size_t) {
delete[] static_cast<char*>(p); delete[] static_cast<char*>(p);
}); });
} }
// Helper to check buffer content // Helper to check buffer content
bool check_buffer_content(const buffer& buf, const std::string& expected) { bool check_buffer_content(const buffer& buf, const std::string& expected) {
if (buf.empty() || buf.size() != expected.size() + 1) { if (buf.empty() || buf.size() != expected.size() + 1) {
return false; return false;
} }
return std::strcmp(static_cast<const char*>(buf.data()), expected.c_str()) == 0; return std::strcmp(static_cast<const char*>(buf.data()), expected.c_str()) == 0;
} }
} // anonymous namespace } // anonymous namespace
@ -57,552 +57,552 @@ bool check_buffer_content(const buffer& buf, const std::string& expected) {
class RouteTest : public ::testing::Test { class RouteTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}; };
// Test default construction // Test default construction
TEST_F(RouteTest, DefaultConstruction) { TEST_F(RouteTest, DefaultConstruction) {
route r; route r;
EXPECT_FALSE(r.valid()); EXPECT_FALSE(r.valid());
} }
// Test construction with name // Test construction with name
TEST_F(RouteTest, ConstructionWithName) { TEST_F(RouteTest, ConstructionWithName) {
std::string name = generate_unique_ipc_name("route_ctor"); std::string name = generate_unique_ipc_name("route_ctor");
route r(name.c_str(), sender); route r(name.c_str(), sender);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
EXPECT_STREQ(r.name(), name.c_str()); EXPECT_STREQ(r.name(), name.c_str());
} }
// Test construction with prefix // Test construction with prefix
TEST_F(RouteTest, ConstructionWithPrefix) { TEST_F(RouteTest, ConstructionWithPrefix) {
std::string name = generate_unique_ipc_name("route_prefix"); std::string name = generate_unique_ipc_name("route_prefix");
route r(prefix{"my_prefix"}, name.c_str(), sender); route r(prefix{"my_prefix"}, name.c_str(), sender);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
} }
// Test move constructor // Test move constructor
TEST_F(RouteTest, MoveConstructor) { TEST_F(RouteTest, MoveConstructor) {
std::string name = generate_unique_ipc_name("route_move"); std::string name = generate_unique_ipc_name("route_move");
route r1(name.c_str(), sender); route r1(name.c_str(), sender);
ASSERT_TRUE(r1.valid()); ASSERT_TRUE(r1.valid());
const char* name_ptr = r1.name(); const char* name_ptr = r1.name();
route r2(std::move(r1)); route r2(std::move(r1));
EXPECT_TRUE(r2.valid()); EXPECT_TRUE(r2.valid());
EXPECT_STREQ(r2.name(), name_ptr); EXPECT_STREQ(r2.name(), name_ptr);
} }
// Test assignment operator // Test assignment operator
TEST_F(RouteTest, Assignment) { TEST_F(RouteTest, Assignment) {
std::string name = generate_unique_ipc_name("route_assign"); std::string name = generate_unique_ipc_name("route_assign");
route r1(name.c_str(), sender); route r1(name.c_str(), sender);
route r2; route r2;
r2 = std::move(r1); r2 = std::move(r1);
EXPECT_TRUE(r2.valid()); EXPECT_TRUE(r2.valid());
} }
// Test connect method // Test connect method
TEST_F(RouteTest, Connect) { TEST_F(RouteTest, Connect) {
std::string name = generate_unique_ipc_name("route_connect"); std::string name = generate_unique_ipc_name("route_connect");
route r; route r;
bool connected = r.connect(name.c_str(), sender); bool connected = r.connect(name.c_str(), sender);
EXPECT_TRUE(connected); EXPECT_TRUE(connected);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
} }
// Test connect with prefix // Test connect with prefix
TEST_F(RouteTest, ConnectWithPrefix) { TEST_F(RouteTest, ConnectWithPrefix) {
std::string name = generate_unique_ipc_name("route_connect_prefix"); std::string name = generate_unique_ipc_name("route_connect_prefix");
route r; route r;
bool connected = r.connect(prefix{"test"}, name.c_str(), sender); bool connected = r.connect(prefix{"test"}, name.c_str(), sender);
EXPECT_TRUE(connected); EXPECT_TRUE(connected);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
} }
// Test reconnect // Test reconnect
TEST_F(RouteTest, Reconnect) { TEST_F(RouteTest, Reconnect) {
std::string name = generate_unique_ipc_name("route_reconnect"); std::string name = generate_unique_ipc_name("route_reconnect");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
bool reconnected = r.reconnect(sender | receiver); bool reconnected = r.reconnect(sender | receiver);
EXPECT_TRUE(reconnected); EXPECT_TRUE(reconnected);
} }
// Test disconnect // Test disconnect
TEST_F(RouteTest, Disconnect) { TEST_F(RouteTest, Disconnect) {
std::string name = generate_unique_ipc_name("route_disconnect"); std::string name = generate_unique_ipc_name("route_disconnect");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
r.disconnect(); r.disconnect();
// After disconnect, behavior depends on implementation // After disconnect, behavior depends on implementation
} }
// Test clone // Test clone
TEST_F(RouteTest, Clone) { TEST_F(RouteTest, Clone) {
std::string name = generate_unique_ipc_name("route_clone"); std::string name = generate_unique_ipc_name("route_clone");
route r1(name.c_str(), sender); route r1(name.c_str(), sender);
ASSERT_TRUE(r1.valid()); ASSERT_TRUE(r1.valid());
route r2 = r1.clone(); route r2 = r1.clone();
EXPECT_TRUE(r2.valid()); EXPECT_TRUE(r2.valid());
EXPECT_STREQ(r1.name(), r2.name()); EXPECT_STREQ(r1.name(), r2.name());
} }
// Test mode accessor // Test mode accessor
TEST_F(RouteTest, Mode) { TEST_F(RouteTest, Mode) {
std::string name = generate_unique_ipc_name("route_mode"); std::string name = generate_unique_ipc_name("route_mode");
route r(name.c_str(), sender); route r(name.c_str(), sender);
EXPECT_EQ(r.mode(), sender); EXPECT_EQ(r.mode(), sender);
} }
// Test release // Test release
TEST_F(RouteTest, Release) { TEST_F(RouteTest, Release) {
std::string name = generate_unique_ipc_name("route_release"); std::string name = generate_unique_ipc_name("route_release");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
r.release(); r.release();
EXPECT_FALSE(r.valid()); EXPECT_FALSE(r.valid());
} }
// Test clear // Test clear
TEST_F(RouteTest, Clear) { TEST_F(RouteTest, Clear) {
std::string name = generate_unique_ipc_name("route_clear"); std::string name = generate_unique_ipc_name("route_clear");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
r.clear(); r.clear();
EXPECT_FALSE(r.valid()); EXPECT_FALSE(r.valid());
} }
// Test clear_storage static method // Test clear_storage static method
TEST_F(RouteTest, ClearStorage) { TEST_F(RouteTest, ClearStorage) {
std::string name = generate_unique_ipc_name("route_clear_storage"); std::string name = generate_unique_ipc_name("route_clear_storage");
{ {
route r(name.c_str(), sender); route r(name.c_str(), sender);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
} }
route::clear_storage(name.c_str()); route::clear_storage(name.c_str());
} }
// Test clear_storage with prefix // Test clear_storage with prefix
TEST_F(RouteTest, ClearStorageWithPrefix) { TEST_F(RouteTest, ClearStorageWithPrefix) {
std::string name = generate_unique_ipc_name("route_clear_prefix"); std::string name = generate_unique_ipc_name("route_clear_prefix");
{ {
route r(prefix{"test"}, name.c_str(), sender); route r(prefix{"test"}, name.c_str(), sender);
EXPECT_TRUE(r.valid()); EXPECT_TRUE(r.valid());
} }
route::clear_storage(prefix{"test"}, name.c_str()); route::clear_storage(prefix{"test"}, name.c_str());
} }
// Test send without receiver (should fail) // Test send without receiver (should fail)
TEST_F(RouteTest, SendWithoutReceiver) { TEST_F(RouteTest, SendWithoutReceiver) {
std::string name = generate_unique_ipc_name("route_send_no_recv"); std::string name = generate_unique_ipc_name("route_send_no_recv");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
buffer buf = make_test_buffer("test"); buffer buf = make_test_buffer("test");
bool sent = r.send(buf, 10); // 10ms timeout bool sent = r.send(buf, 10); // 10ms timeout
EXPECT_FALSE(sent); // Should fail - no receiver EXPECT_FALSE(sent); // Should fail - no receiver
} }
// Test try_send without receiver // Test try_send without receiver
TEST_F(RouteTest, TrySendWithoutReceiver) { TEST_F(RouteTest, TrySendWithoutReceiver) {
std::string name = generate_unique_ipc_name("route_try_send_no_recv"); std::string name = generate_unique_ipc_name("route_try_send_no_recv");
route r(name.c_str(), sender); route r(name.c_str(), sender);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
buffer buf = make_test_buffer("test"); buffer buf = make_test_buffer("test");
bool sent = r.try_send(buf, 10); bool sent = r.try_send(buf, 10);
EXPECT_FALSE(sent); EXPECT_FALSE(sent);
} }
// Test send and receive with buffer // Test send and receive with buffer
TEST_F(RouteTest, SendReceiveBuffer) { TEST_F(RouteTest, SendReceiveBuffer) {
std::string name = generate_unique_ipc_name("route_send_recv_buf"); std::string name = generate_unique_ipc_name("route_send_recv_buf");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid()); ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid()); ASSERT_TRUE(receiver_r.valid());
buffer send_buf = make_test_buffer("Hello Route"); buffer send_buf = make_test_buffer("Hello Route");
std::thread sender_thread([&]() { std::thread sender_thread([&]() {
bool sent = sender_r.send(send_buf); bool sent = sender_r.send(send_buf);
EXPECT_TRUE(sent); EXPECT_TRUE(sent);
}); });
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv(); buffer recv_buf = receiver_r.recv();
EXPECT_TRUE(check_buffer_content(recv_buf, "Hello Route")); EXPECT_TRUE(check_buffer_content(recv_buf, "Hello Route"));
}); });
sender_thread.join(); sender_thread.join();
receiver_thread.join(); receiver_thread.join();
} }
// Test send and receive with string // Test send and receive with string
TEST_F(RouteTest, SendReceiveString) { TEST_F(RouteTest, SendReceiveString) {
std::string name = generate_unique_ipc_name("route_send_recv_str"); std::string name = generate_unique_ipc_name("route_send_recv_str");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid()); ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid()); ASSERT_TRUE(receiver_r.valid());
std::string test_str = "Test String"; std::string test_str = "Test String";
std::thread sender_thread([&]() { std::thread sender_thread([&]() {
bool sent = sender_r.send(test_str); bool sent = sender_r.send(test_str);
EXPECT_TRUE(sent); EXPECT_TRUE(sent);
}); });
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv(); buffer recv_buf = receiver_r.recv();
EXPECT_TRUE(check_buffer_content(recv_buf, test_str)); EXPECT_TRUE(check_buffer_content(recv_buf, test_str));
}); });
sender_thread.join(); sender_thread.join();
receiver_thread.join(); receiver_thread.join();
} }
// Test send and receive with raw data // Test send and receive with raw data
TEST_F(RouteTest, SendReceiveRawData) { TEST_F(RouteTest, SendReceiveRawData) {
std::string name = generate_unique_ipc_name("route_send_recv_raw"); std::string name = generate_unique_ipc_name("route_send_recv_raw");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid()); ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid()); ASSERT_TRUE(receiver_r.valid());
const char* data = "Raw Data Test"; const char* data = "Raw Data Test";
std::size_t size = std::strlen(data) + 1; std::size_t size = std::strlen(data) + 1;
std::thread sender_thread([&]() { std::thread sender_thread([&]() {
bool sent = sender_r.send(data, size); bool sent = sender_r.send(data, size);
EXPECT_TRUE(sent); EXPECT_TRUE(sent);
}); });
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv(); buffer recv_buf = receiver_r.recv();
EXPECT_EQ(recv_buf.size(), size); EXPECT_EQ(recv_buf.size(), size);
EXPECT_STREQ(static_cast<const char*>(recv_buf.data()), data); EXPECT_STREQ(static_cast<const char*>(recv_buf.data()), data);
}); });
sender_thread.join(); sender_thread.join();
receiver_thread.join(); receiver_thread.join();
} }
// Test try_recv when empty // Test try_recv when empty
TEST_F(RouteTest, TryRecvEmpty) { TEST_F(RouteTest, TryRecvEmpty) {
std::string name = generate_unique_ipc_name("route_try_recv_empty"); std::string name = generate_unique_ipc_name("route_try_recv_empty");
route r(name.c_str(), receiver); route r(name.c_str(), receiver);
ASSERT_TRUE(r.valid()); ASSERT_TRUE(r.valid());
buffer buf = r.try_recv(); buffer buf = r.try_recv();
EXPECT_TRUE(buf.empty()); EXPECT_TRUE(buf.empty());
} }
// Test recv_count // Test recv_count
TEST_F(RouteTest, RecvCount) { TEST_F(RouteTest, RecvCount) {
std::string name = generate_unique_ipc_name("route_recv_count"); std::string name = generate_unique_ipc_name("route_recv_count");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid()); ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid()); ASSERT_TRUE(receiver_r.valid());
std::size_t count = sender_r.recv_count(); std::size_t count = sender_r.recv_count();
EXPECT_GE(count, 0u); EXPECT_GE(count, 0u);
} }
// Test wait_for_recv // Test wait_for_recv
TEST_F(RouteTest, WaitForRecv) { TEST_F(RouteTest, WaitForRecv) {
std::string name = generate_unique_ipc_name("route_wait_recv"); std::string name = generate_unique_ipc_name("route_wait_recv");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
}); });
bool waited = sender_r.wait_for_recv(1, 500); bool waited = sender_r.wait_for_recv(1, 500);
receiver_thread.join(); receiver_thread.join();
// Result depends on timing // Result depends on timing
} }
// Test static wait_for_recv // Test static wait_for_recv
TEST_F(RouteTest, StaticWaitForRecv) { TEST_F(RouteTest, StaticWaitForRecv) {
std::string name = generate_unique_ipc_name("route_static_wait"); std::string name = generate_unique_ipc_name("route_static_wait");
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
}); });
bool waited = route::wait_for_recv(name.c_str(), 1, 500); bool waited = route::wait_for_recv(name.c_str(), 1, 500);
receiver_thread.join(); receiver_thread.join();
} }
// Test one sender, multiple receivers // Test one sender, multiple receivers
TEST_F(RouteTest, OneSenderMultipleReceivers) { TEST_F(RouteTest, OneSenderMultipleReceivers) {
std::string name = generate_unique_ipc_name("route_1_to_n"); std::string name = generate_unique_ipc_name("route_1_to_n");
route sender_r(name.c_str(), sender); route sender_r(name.c_str(), sender);
ASSERT_TRUE(sender_r.valid()); ASSERT_TRUE(sender_r.valid());
const int num_receivers = 3; const int num_receivers = 3;
std::vector<std::atomic<bool>> received(num_receivers); std::vector<std::atomic<bool>> received(num_receivers);
for (auto& r : received) r.store(false); for (auto& r : received) r.store(false);
std::vector<std::thread> receivers; std::vector<std::thread> receivers;
for (int i = 0; i < num_receivers; ++i) { for (int i = 0; i < num_receivers; ++i) {
receivers.emplace_back([&, i]() { receivers.emplace_back([&, i]() {
route receiver_r(name.c_str(), receiver); route receiver_r(name.c_str(), receiver);
buffer buf = receiver_r.recv(1000); buffer buf = receiver_r.recv(1000);
if (check_buffer_content(buf, "Broadcast")) { if (check_buffer_content(buf, "Broadcast")) {
received[i].store(true); received[i].store(true);
} }
}); });
} }
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
sender_r.send(std::string("Broadcast")); sender_r.send(std::string("Broadcast"));
for (auto& t : receivers) { for (auto& t : receivers) {
t.join(); t.join();
} }
// All receivers should receive the message (broadcast) // All receivers should receive the message (broadcast)
for (const auto& r : received) { for (const auto& r : received) {
EXPECT_TRUE(r.load()); EXPECT_TRUE(r.load());
} }
} }
// ========== Channel Tests (Multiple Producer, Multiple Consumer) ========== // ========== Channel Tests (Multiple Producer, Multiple Consumer) ==========
class ChannelTest : public ::testing::Test { class ChannelTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}; };
// Test default construction // Test default construction
TEST_F(ChannelTest, DefaultConstruction) { TEST_F(ChannelTest, DefaultConstruction) {
channel ch; channel ch;
EXPECT_FALSE(ch.valid()); EXPECT_FALSE(ch.valid());
} }
// Test construction with name // Test construction with name
TEST_F(ChannelTest, ConstructionWithName) { TEST_F(ChannelTest, ConstructionWithName) {
std::string name = generate_unique_ipc_name("channel_ctor"); std::string name = generate_unique_ipc_name("channel_ctor");
channel ch(name.c_str(), sender); channel ch(name.c_str(), sender);
EXPECT_TRUE(ch.valid()); EXPECT_TRUE(ch.valid());
EXPECT_STREQ(ch.name(), name.c_str()); EXPECT_STREQ(ch.name(), name.c_str());
} }
// Test send and receive // Test send and receive
TEST_F(ChannelTest, SendReceive) { TEST_F(ChannelTest, SendReceive) {
std::string name = generate_unique_ipc_name("channel_send_recv"); std::string name = generate_unique_ipc_name("channel_send_recv");
channel sender_ch(name.c_str(), sender); channel sender_ch(name.c_str(), sender);
channel receiver_ch(name.c_str(), receiver); channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(sender_ch.valid()); ASSERT_TRUE(sender_ch.valid());
ASSERT_TRUE(receiver_ch.valid()); ASSERT_TRUE(receiver_ch.valid());
std::thread sender_thread([&]() { std::thread sender_thread([&]() {
sender_ch.send(std::string("Channel Test")); sender_ch.send(std::string("Channel Test"));
}); });
std::thread receiver_thread([&]() { std::thread receiver_thread([&]() {
buffer buf = receiver_ch.recv(); buffer buf = receiver_ch.recv();
EXPECT_TRUE(check_buffer_content(buf, "Channel Test")); EXPECT_TRUE(check_buffer_content(buf, "Channel Test"));
}); });
sender_thread.join(); sender_thread.join();
receiver_thread.join(); receiver_thread.join();
} }
// Test multiple senders // Test multiple senders
TEST_F(ChannelTest, MultipleSenders) { TEST_F(ChannelTest, MultipleSenders) {
std::string name = generate_unique_ipc_name("channel_multi_send"); std::string name = generate_unique_ipc_name("channel_multi_send");
channel receiver_ch(name.c_str(), receiver); channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(receiver_ch.valid()); ASSERT_TRUE(receiver_ch.valid());
const int num_senders = 3; const int num_senders = 3;
std::atomic<int> received_count{0}; std::atomic<int> received_count{0};
std::vector<std::thread> senders; std::vector<std::thread> senders;
for (int i = 0; i < num_senders; ++i) { for (int i = 0; i < num_senders; ++i) {
senders.emplace_back([&, i]() { senders.emplace_back([&, i]() {
channel sender_ch(name.c_str(), sender); channel sender_ch(name.c_str(), sender);
std::string msg = "Sender" + std::to_string(i); std::string msg = "Sender" + std::to_string(i);
sender_ch.send(msg); sender_ch.send(msg);
}); });
} }
std::thread receiver([&]() { std::thread receiver([&]() {
for (int i = 0; i < num_senders; ++i) { for (int i = 0; i < num_senders; ++i) {
buffer buf = receiver_ch.recv(1000); buffer buf = receiver_ch.recv(1000);
if (!buf.empty()) { if (!buf.empty()) {
++received_count; ++received_count;
} }
} }
}); });
for (auto& t : senders) { for (auto& t : senders) {
t.join(); t.join();
} }
receiver.join(); receiver.join();
EXPECT_EQ(received_count.load(), num_senders); EXPECT_EQ(received_count.load(), num_senders);
} }
// Test multiple senders and receivers // Test multiple senders and receivers
TEST_F(ChannelTest, MultipleSendersReceivers) { TEST_F(ChannelTest, MultipleSendersReceivers) {
std::string name = generate_unique_ipc_name("channel_m_to_n"); std::string name = generate_unique_ipc_name("channel_m_to_n");
const int num_senders = 2; const int num_senders = 2;
const int num_receivers = 2; const int num_receivers = 2;
const int messages_per_sender = 5; const int messages_per_sender = 5;
std::atomic<int> sent_count{0}; std::atomic<int> sent_count{0};
std::atomic<int> received_count{0}; std::atomic<int> received_count{0};
std::vector<std::thread> senders; std::vector<std::thread> senders;
for (int i = 0; i < num_senders; ++i) { for (int i = 0; i < num_senders; ++i) {
senders.emplace_back([&, i]() { senders.emplace_back([&, i]() {
channel ch(name.c_str(), sender); channel ch(name.c_str(), sender);
for (int j = 0; j < messages_per_sender; ++j) { for (int j = 0; j < messages_per_sender; ++j) {
std::string msg = "S" + std::to_string(i) + "M" + std::to_string(j); std::string msg = "S" + std::to_string(i) + "M" + std::to_string(j);
if (ch.send(msg, 1000)) { if (ch.send(msg, 1000)) {
++sent_count; ++sent_count;
} }
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}); });
} }
std::vector<std::thread> receivers; std::vector<std::thread> receivers;
for (int i = 0; i < num_receivers; ++i) { for (int i = 0; i < num_receivers; ++i) {
receivers.emplace_back([&, i]() { receivers.emplace_back([&, i]() {
channel ch(name.c_str(), receiver); channel ch(name.c_str(), receiver);
for (int j = 0; j < messages_per_sender; ++j) { for (int j = 0; j < messages_per_sender; ++j) {
buffer buf = ch.recv(2000); buffer buf = ch.recv(2000);
if (!buf.empty()) { if (!buf.empty()) {
++received_count; ++received_count;
} }
} }
}); });
} }
for (auto& t : senders) { for (auto& t : senders) {
t.join(); t.join();
} }
for (auto& t : receivers) { for (auto& t : receivers) {
t.join(); t.join();
} }
EXPECT_EQ(sent_count.load(), num_senders * messages_per_sender); EXPECT_EQ(sent_count.load(), num_senders * messages_per_sender);
// All messages should be received (broadcast mode) // All messages should be received (broadcast mode)
EXPECT_EQ(received_count.load(), num_senders * messages_per_sender * num_receivers); EXPECT_EQ(received_count.load(), num_senders * messages_per_sender * num_receivers);
} }
// Test try_send and try_recv // Test try_send and try_recv
TEST_F(ChannelTest, TrySendTryRecv) { TEST_F(ChannelTest, TrySendTryRecv) {
std::string name = generate_unique_ipc_name("channel_try"); std::string name = generate_unique_ipc_name("channel_try");
channel sender_ch(name.c_str(), sender); channel sender_ch(name.c_str(), sender);
channel receiver_ch(name.c_str(), receiver); channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(sender_ch.valid()); ASSERT_TRUE(sender_ch.valid());
ASSERT_TRUE(receiver_ch.valid()); ASSERT_TRUE(receiver_ch.valid());
bool sent = sender_ch.try_send(std::string("Try Test")); bool sent = sender_ch.try_send(std::string("Try Test"));
if (sent) { if (sent) {
buffer buf = receiver_ch.try_recv(); buffer buf = receiver_ch.try_recv();
EXPECT_FALSE(buf.empty()); EXPECT_FALSE(buf.empty());
} }
} }
// Test timeout scenarios // Test timeout scenarios
TEST_F(ChannelTest, SendTimeout) { TEST_F(ChannelTest, SendTimeout) {
std::string name = generate_unique_ipc_name("channel_timeout"); std::string name = generate_unique_ipc_name("channel_timeout");
channel ch(name.c_str(), sender); channel ch(name.c_str(), sender);
ASSERT_TRUE(ch.valid()); ASSERT_TRUE(ch.valid());
// Send with very short timeout (may fail without receiver) // Send with very short timeout (may fail without receiver)
bool sent = ch.send(std::string("Timeout Test"), 1); bool sent = ch.send(std::string("Timeout Test"), 1);
} }
// Test clear and clear_storage // Test clear and clear_storage
TEST_F(ChannelTest, ClearStorage) { TEST_F(ChannelTest, ClearStorage) {
std::string name = generate_unique_ipc_name("channel_clear"); std::string name = generate_unique_ipc_name("channel_clear");
{ {
channel ch(name.c_str(), sender); channel ch(name.c_str(), sender);
EXPECT_TRUE(ch.valid()); EXPECT_TRUE(ch.valid());
} }
channel::clear_storage(name.c_str()); channel::clear_storage(name.c_str());
} }
// Test handle() method // Test handle() method
TEST_F(ChannelTest, Handle) { TEST_F(ChannelTest, Handle) {
std::string name = generate_unique_ipc_name("channel_handle"); std::string name = generate_unique_ipc_name("channel_handle");
channel ch(name.c_str(), sender); channel ch(name.c_str(), sender);
ASSERT_TRUE(ch.valid()); ASSERT_TRUE(ch.valid());
handle_t h = ch.handle(); handle_t h = ch.handle();
EXPECT_NE(h, nullptr); EXPECT_NE(h, nullptr);
} }

View File

@ -24,584 +24,584 @@ using namespace ipc;
class SpinLockTest : public ::testing::Test { class SpinLockTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
}; };
// Test basic lock and unlock // Test basic lock and unlock
TEST_F(SpinLockTest, BasicLockUnlock) { TEST_F(SpinLockTest, BasicLockUnlock) {
spin_lock lock; spin_lock lock;
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
// Should complete without hanging // Should complete without hanging
} }
// Test multiple lock/unlock cycles // Test multiple lock/unlock cycles
TEST_F(SpinLockTest, MultipleCycles) { TEST_F(SpinLockTest, MultipleCycles) {
spin_lock lock; spin_lock lock;
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
} }
} }
// Test critical section protection // Test critical section protection
TEST_F(SpinLockTest, CriticalSection) { TEST_F(SpinLockTest, CriticalSection) {
spin_lock lock; spin_lock lock;
int counter = 0; int counter = 0;
const int iterations = 1000; const int iterations = 1000;
auto increment_task = [&]() { auto increment_task = [&]() {
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
lock.lock(); lock.lock();
++counter; ++counter;
lock.unlock(); lock.unlock();
} }
}; };
std::thread t1(increment_task); std::thread t1(increment_task);
std::thread t2(increment_task); std::thread t2(increment_task);
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(counter, iterations * 2); EXPECT_EQ(counter, iterations * 2);
} }
// Test mutual exclusion // Test mutual exclusion
TEST_F(SpinLockTest, MutualExclusion) { TEST_F(SpinLockTest, MutualExclusion) {
spin_lock lock; spin_lock lock;
std::atomic<bool> thread1_in_cs{false}; std::atomic<bool> thread1_in_cs{false};
std::atomic<bool> thread2_in_cs{false}; std::atomic<bool> thread2_in_cs{false};
std::atomic<bool> violation{false}; std::atomic<bool> violation{false};
auto cs_task = [&](std::atomic<bool>& my_flag, std::atomic<bool>& other_flag) { auto cs_task = [&](std::atomic<bool>& my_flag, std::atomic<bool>& other_flag) {
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
lock.lock(); lock.lock();
my_flag.store(true); my_flag.store(true);
if (other_flag.load()) { if (other_flag.load()) {
violation.store(true); violation.store(true);
} }
std::this_thread::sleep_for(std::chrono::microseconds(10)); std::this_thread::sleep_for(std::chrono::microseconds(10));
my_flag.store(false); my_flag.store(false);
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(cs_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs)); std::thread t1(cs_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs));
std::thread t2(cs_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs)); std::thread t2(cs_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs));
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_FALSE(violation.load()); EXPECT_FALSE(violation.load());
} }
// Test concurrent access // Test concurrent access
TEST_F(SpinLockTest, ConcurrentAccess) { TEST_F(SpinLockTest, ConcurrentAccess) {
spin_lock lock; spin_lock lock;
std::atomic<int> shared_data{0}; std::atomic<int> shared_data{0};
const int num_threads = 4; const int num_threads = 4;
const int ops_per_thread = 100; const int ops_per_thread = 100;
std::vector<std::thread> threads; std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) { for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() { threads.emplace_back([&]() {
for (int j = 0; j < ops_per_thread; ++j) { for (int j = 0; j < ops_per_thread; ++j) {
lock.lock(); lock.lock();
int temp = shared_data.load(); int temp = shared_data.load();
std::this_thread::yield(); std::this_thread::yield();
shared_data.store(temp + 1); shared_data.store(temp + 1);
lock.unlock(); lock.unlock();
} }
}); });
} }
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
EXPECT_EQ(shared_data.load(), num_threads * ops_per_thread); EXPECT_EQ(shared_data.load(), num_threads * ops_per_thread);
} }
// Test rapid lock/unlock // Test rapid lock/unlock
TEST_F(SpinLockTest, RapidLockUnlock) { TEST_F(SpinLockTest, RapidLockUnlock) {
spin_lock lock; spin_lock lock;
auto rapid_task = [&]() { auto rapid_task = [&]() {
for (int i = 0; i < 10000; ++i) { for (int i = 0; i < 10000; ++i) {
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
} }
}; };
std::thread t1(rapid_task); std::thread t1(rapid_task);
std::thread t2(rapid_task); std::thread t2(rapid_task);
t1.join(); t1.join();
t2.join(); t2.join();
// Should complete without deadlock // Should complete without deadlock
} }
// Test contention scenario // Test contention scenario
TEST_F(SpinLockTest, Contention) { TEST_F(SpinLockTest, Contention) {
spin_lock lock; spin_lock lock;
std::atomic<int> work_done{0}; std::atomic<int> work_done{0};
const int num_threads = 8; const int num_threads = 8;
std::vector<std::thread> threads; std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) { for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() { threads.emplace_back([&]() {
for (int j = 0; j < 50; ++j) { for (int j = 0; j < 50; ++j) {
lock.lock(); lock.lock();
++work_done; ++work_done;
std::this_thread::sleep_for(std::chrono::microseconds(100)); std::this_thread::sleep_for(std::chrono::microseconds(100));
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}); });
} }
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
EXPECT_EQ(work_done.load(), num_threads * 50); EXPECT_EQ(work_done.load(), num_threads * 50);
} }
// ========== rw_lock Tests ========== // ========== rw_lock Tests ==========
class RWLockTest : public ::testing::Test { class RWLockTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
}; };
// Test basic write lock and unlock // Test basic write lock and unlock
TEST_F(RWLockTest, BasicWriteLock) { TEST_F(RWLockTest, BasicWriteLock) {
rw_lock lock; rw_lock lock;
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
// Should complete without hanging // Should complete without hanging
} }
// Test basic read lock and unlock // Test basic read lock and unlock
TEST_F(RWLockTest, BasicReadLock) { TEST_F(RWLockTest, BasicReadLock) {
rw_lock lock; rw_lock lock;
lock.lock_shared(); lock.lock_shared();
lock.unlock_shared(); lock.unlock_shared();
// Should complete without hanging // Should complete without hanging
} }
// Test multiple write cycles // Test multiple write cycles
TEST_F(RWLockTest, MultipleWriteCycles) { TEST_F(RWLockTest, MultipleWriteCycles) {
rw_lock lock; rw_lock lock;
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
} }
} }
// Test multiple read cycles // Test multiple read cycles
TEST_F(RWLockTest, MultipleReadCycles) { TEST_F(RWLockTest, MultipleReadCycles) {
rw_lock lock; rw_lock lock;
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
lock.lock_shared(); lock.lock_shared();
lock.unlock_shared(); lock.unlock_shared();
} }
} }
// Test write lock protects data // Test write lock protects data
TEST_F(RWLockTest, WriteLockProtection) { TEST_F(RWLockTest, WriteLockProtection) {
rw_lock lock; rw_lock lock;
int data = 0; int data = 0;
const int iterations = 500; const int iterations = 500;
auto writer_task = [&]() { auto writer_task = [&]() {
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
lock.lock(); lock.lock();
++data; ++data;
lock.unlock(); lock.unlock();
} }
}; };
std::thread t1(writer_task); std::thread t1(writer_task);
std::thread t2(writer_task); std::thread t2(writer_task);
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(data, iterations * 2); EXPECT_EQ(data, iterations * 2);
} }
// Test multiple readers can access concurrently // Test multiple readers can access concurrently
TEST_F(RWLockTest, ConcurrentReaders) { TEST_F(RWLockTest, ConcurrentReaders) {
rw_lock lock; rw_lock lock;
std::atomic<int> concurrent_readers{0}; std::atomic<int> concurrent_readers{0};
std::atomic<int> max_concurrent{0}; std::atomic<int> max_concurrent{0};
const int num_readers = 5; const int num_readers = 5;
std::vector<std::thread> readers; std::vector<std::thread> readers;
for (int i = 0; i < num_readers; ++i) { for (int i = 0; i < num_readers; ++i) {
readers.emplace_back([&]() { readers.emplace_back([&]() {
for (int j = 0; j < 20; ++j) { for (int j = 0; j < 20; ++j) {
lock.lock_shared(); lock.lock_shared();
int current = ++concurrent_readers; int current = ++concurrent_readers;
// Track maximum concurrent readers // Track maximum concurrent readers
int current_max = max_concurrent.load(); int current_max = max_concurrent.load();
while (current > current_max) { while (current > current_max) {
if (max_concurrent.compare_exchange_weak(current_max, current)) { if (max_concurrent.compare_exchange_weak(current_max, current)) {
break; break;
} }
} }
std::this_thread::sleep_for(std::chrono::microseconds(100)); std::this_thread::sleep_for(std::chrono::microseconds(100));
--concurrent_readers; --concurrent_readers;
lock.unlock_shared(); lock.unlock_shared();
std::this_thread::yield(); std::this_thread::yield();
} }
}); });
} }
for (auto& t : readers) { for (auto& t : readers) {
t.join(); t.join();
} }
// Should have had multiple concurrent readers // Should have had multiple concurrent readers
EXPECT_GT(max_concurrent.load(), 1); EXPECT_GT(max_concurrent.load(), 1);
} }
// Test writers have exclusive access // Test writers have exclusive access
TEST_F(RWLockTest, WriterExclusiveAccess) { TEST_F(RWLockTest, WriterExclusiveAccess) {
rw_lock lock; rw_lock lock;
std::atomic<bool> writer_in_cs{false}; std::atomic<bool> writer_in_cs{false};
std::atomic<bool> violation{false}; std::atomic<bool> violation{false};
auto writer_task = [&]() { auto writer_task = [&]() {
for (int i = 0; i < 50; ++i) { for (int i = 0; i < 50; ++i) {
lock.lock(); lock.lock();
if (writer_in_cs.exchange(true)) { if (writer_in_cs.exchange(true)) {
violation.store(true); violation.store(true);
} }
std::this_thread::sleep_for(std::chrono::microseconds(50)); std::this_thread::sleep_for(std::chrono::microseconds(50));
writer_in_cs.store(false); writer_in_cs.store(false);
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(writer_task); std::thread t1(writer_task);
std::thread t2(writer_task); std::thread t2(writer_task);
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_FALSE(violation.load()); EXPECT_FALSE(violation.load());
} }
// Test readers and writers don't overlap // Test readers and writers don't overlap
TEST_F(RWLockTest, ReadersWritersNoOverlap) { TEST_F(RWLockTest, ReadersWritersNoOverlap) {
rw_lock lock; rw_lock lock;
std::atomic<int> readers{0}; std::atomic<int> readers{0};
std::atomic<bool> writer_active{false}; std::atomic<bool> writer_active{false};
std::atomic<bool> violation{false}; std::atomic<bool> violation{false};
auto reader_task = [&]() { auto reader_task = [&]() {
for (int i = 0; i < 30; ++i) { for (int i = 0; i < 30; ++i) {
lock.lock_shared(); lock.lock_shared();
++readers; ++readers;
if (writer_active.load()) { if (writer_active.load()) {
violation.store(true); violation.store(true);
} }
std::this_thread::sleep_for(std::chrono::microseconds(50)); std::this_thread::sleep_for(std::chrono::microseconds(50));
--readers; --readers;
lock.unlock_shared(); lock.unlock_shared();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
auto writer_task = [&]() { auto writer_task = [&]() {
for (int i = 0; i < 15; ++i) { for (int i = 0; i < 15; ++i) {
lock.lock(); lock.lock();
writer_active.store(true); writer_active.store(true);
if (readers.load() > 0) { if (readers.load() > 0) {
violation.store(true); violation.store(true);
} }
std::this_thread::sleep_for(std::chrono::microseconds(50)); std::this_thread::sleep_for(std::chrono::microseconds(50));
writer_active.store(false); writer_active.store(false);
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread r1(reader_task); std::thread r1(reader_task);
std::thread r2(reader_task); std::thread r2(reader_task);
std::thread w1(writer_task); std::thread w1(writer_task);
r1.join(); r1.join();
r2.join(); r2.join();
w1.join(); w1.join();
EXPECT_FALSE(violation.load()); EXPECT_FALSE(violation.load());
} }
// Test read-write-read pattern // Test read-write-read pattern
TEST_F(RWLockTest, ReadWriteReadPattern) { TEST_F(RWLockTest, ReadWriteReadPattern) {
rw_lock lock; rw_lock lock;
int data = 0; int data = 0;
auto pattern_task = [&](int id) { auto pattern_task = [&](int id) {
for (int i = 0; i < 20; ++i) { for (int i = 0; i < 20; ++i) {
// Read // Read
lock.lock_shared(); lock.lock_shared();
int read_val = data; int read_val = data;
lock.unlock_shared(); lock.unlock_shared();
std::this_thread::yield(); std::this_thread::yield();
// Write // Write
lock.lock(); lock.lock();
data = read_val + 1; data = read_val + 1;
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(pattern_task, 1); std::thread t1(pattern_task, 1);
std::thread t2(pattern_task, 2); std::thread t2(pattern_task, 2);
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(data, 40); EXPECT_EQ(data, 40);
} }
// Test many readers, one writer // Test many readers, one writer
TEST_F(RWLockTest, ManyReadersOneWriter) { TEST_F(RWLockTest, ManyReadersOneWriter) {
rw_lock lock; rw_lock lock;
std::atomic<int> data{0}; std::atomic<int> data{0};
std::atomic<int> read_count{0}; std::atomic<int> read_count{0};
const int num_readers = 10; const int num_readers = 10;
std::vector<std::thread> readers; std::vector<std::thread> readers;
for (int i = 0; i < num_readers; ++i) { for (int i = 0; i < num_readers; ++i) {
readers.emplace_back([&]() { readers.emplace_back([&]() {
for (int j = 0; j < 50; ++j) { for (int j = 0; j < 50; ++j) {
lock.lock_shared(); lock.lock_shared();
int val = data.load(); int val = data.load();
++read_count; ++read_count;
lock.unlock_shared(); lock.unlock_shared();
std::this_thread::yield(); std::this_thread::yield();
} }
}); });
} }
std::thread writer([&]() { std::thread writer([&]() {
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
lock.lock(); lock.lock();
data.store(data.load() + 1); data.store(data.load() + 1);
lock.unlock(); lock.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}); });
for (auto& t : readers) { for (auto& t : readers) {
t.join(); t.join();
} }
writer.join(); writer.join();
EXPECT_EQ(data.load(), 100); EXPECT_EQ(data.load(), 100);
EXPECT_EQ(read_count.load(), num_readers * 50); EXPECT_EQ(read_count.load(), num_readers * 50);
} }
// Test rapid read lock/unlock // Test rapid read lock/unlock
TEST_F(RWLockTest, RapidReadLocks) { TEST_F(RWLockTest, RapidReadLocks) {
rw_lock lock; rw_lock lock;
auto rapid_read = [&]() { auto rapid_read = [&]() {
for (int i = 0; i < 5000; ++i) { for (int i = 0; i < 5000; ++i) {
lock.lock_shared(); lock.lock_shared();
lock.unlock_shared(); lock.unlock_shared();
} }
}; };
std::thread t1(rapid_read); std::thread t1(rapid_read);
std::thread t2(rapid_read); std::thread t2(rapid_read);
std::thread t3(rapid_read); std::thread t3(rapid_read);
t1.join(); t1.join();
t2.join(); t2.join();
t3.join(); t3.join();
} }
// Test rapid write lock/unlock // Test rapid write lock/unlock
TEST_F(RWLockTest, RapidWriteLocks) { TEST_F(RWLockTest, RapidWriteLocks) {
rw_lock lock; rw_lock lock;
auto rapid_write = [&]() { auto rapid_write = [&]() {
for (int i = 0; i < 2000; ++i) { for (int i = 0; i < 2000; ++i) {
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
} }
}; };
std::thread t1(rapid_write); std::thread t1(rapid_write);
std::thread t2(rapid_write); std::thread t2(rapid_write);
t1.join(); t1.join();
t2.join(); t2.join();
} }
// Test mixed rapid operations // Test mixed rapid operations
TEST_F(RWLockTest, MixedRapidOperations) { TEST_F(RWLockTest, MixedRapidOperations) {
rw_lock lock; rw_lock lock;
auto rapid_read = [&]() { auto rapid_read = [&]() {
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
lock.lock_shared(); lock.lock_shared();
lock.unlock_shared(); lock.unlock_shared();
} }
}; };
auto rapid_write = [&]() { auto rapid_write = [&]() {
for (int i = 0; i < 500; ++i) { for (int i = 0; i < 500; ++i) {
lock.lock(); lock.lock();
lock.unlock(); lock.unlock();
} }
}; };
std::thread r1(rapid_read); std::thread r1(rapid_read);
std::thread r2(rapid_read); std::thread r2(rapid_read);
std::thread w1(rapid_write); std::thread w1(rapid_write);
r1.join(); r1.join();
r2.join(); r2.join();
w1.join(); w1.join();
} }
// Test write lock doesn't allow concurrent readers // Test write lock doesn't allow concurrent readers
TEST_F(RWLockTest, WriteLockBlocksReaders) { TEST_F(RWLockTest, WriteLockBlocksReaders) {
rw_lock lock; rw_lock lock;
std::atomic<bool> write_locked{false}; std::atomic<bool> write_locked{false};
std::atomic<bool> reader_entered{false}; std::atomic<bool> reader_entered{false};
std::thread writer([&]() { std::thread writer([&]() {
lock.lock(); lock.lock();
write_locked.store(true); write_locked.store(true);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
write_locked.store(false); write_locked.store(false);
lock.unlock(); lock.unlock();
}); });
std::thread reader([&]() { std::thread reader([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(20)); std::this_thread::sleep_for(std::chrono::milliseconds(20));
lock.lock_shared(); lock.lock_shared();
if (write_locked.load()) { if (write_locked.load()) {
reader_entered.store(true); reader_entered.store(true);
} }
lock.unlock_shared(); lock.unlock_shared();
}); });
writer.join(); writer.join();
reader.join(); reader.join();
// Reader should not have entered while writer held the lock // Reader should not have entered while writer held the lock
EXPECT_FALSE(reader_entered.load()); EXPECT_FALSE(reader_entered.load());
} }
// Test multiple write lock upgrades // Test multiple write lock upgrades
TEST_F(RWLockTest, MultipleWriteLockPattern) { TEST_F(RWLockTest, MultipleWriteLockPattern) {
rw_lock lock; rw_lock lock;
int data = 0; int data = 0;
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
// Read // Read
lock.lock_shared(); lock.lock_shared();
int temp = data; int temp = data;
lock.unlock_shared(); lock.unlock_shared();
// Write // Write
lock.lock(); lock.lock();
data = temp + 1; data = temp + 1;
lock.unlock(); lock.unlock();
} }
EXPECT_EQ(data, 100); EXPECT_EQ(data, 100);
} }
// Test concurrent mixed operations // Test concurrent mixed operations
TEST_F(RWLockTest, ConcurrentMixedOperations) { TEST_F(RWLockTest, ConcurrentMixedOperations) {
rw_lock lock; rw_lock lock;
std::atomic<int> data{0}; std::atomic<int> data{0};
std::atomic<int> reads{0}; std::atomic<int> reads{0};
std::atomic<int> writes{0}; std::atomic<int> writes{0};
auto mixed_task = [&](int id) { auto mixed_task = [&](int id) {
for (int i = 0; i < 50; ++i) { for (int i = 0; i < 50; ++i) {
if (i % 3 == 0) { if (i % 3 == 0) {
// Write operation // Write operation
lock.lock(); lock.lock();
data.store(data.load() + 1); data.store(data.load() + 1);
++writes; ++writes;
lock.unlock(); lock.unlock();
} else { } else {
// Read operation // Read operation
lock.lock_shared(); lock.lock_shared();
int val = data.load(); int val = data.load();
++reads; ++reads;
lock.unlock_shared(); lock.unlock_shared();
} }
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(mixed_task, 1); std::thread t1(mixed_task, 1);
std::thread t2(mixed_task, 2); std::thread t2(mixed_task, 2);
std::thread t3(mixed_task, 3); std::thread t3(mixed_task, 3);
std::thread t4(mixed_task, 4); std::thread t4(mixed_task, 4);
t1.join(); t1.join();
t2.join(); t2.join();
t3.join(); t3.join();
t4.join(); t4.join();
EXPECT_GT(reads.load(), 0); EXPECT_GT(reads.load(), 0);
EXPECT_GT(writes.load(), 0); EXPECT_GT(writes.load(), 0);
} }

View File

@ -28,474 +28,474 @@ namespace {
// Generate unique mutex names for tests // Generate unique mutex names for tests
std::string generate_unique_mutex_name(const char* prefix) { std::string generate_unique_mutex_name(const char* prefix) {
static int counter = 0; static int counter = 0;
return std::string(prefix) + "_mutex_" + std::to_string(++counter); return std::string(prefix) + "_mutex_" + std::to_string(++counter);
} }
} // anonymous namespace } // anonymous namespace
class MutexTest : public ::testing::Test { class MutexTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
// Allow time for cleanup // Allow time for cleanup
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}; };
// Test default constructor // Test default constructor
TEST_F(MutexTest, DefaultConstructor) { TEST_F(MutexTest, DefaultConstructor) {
mutex mtx; mutex mtx;
// Default constructed mutex may or may not be valid depending on implementation // Default constructed mutex may or may not be valid depending on implementation
// Just ensure it doesn't crash // Just ensure it doesn't crash
} }
// Test named constructor // Test named constructor
TEST_F(MutexTest, NamedConstructor) { TEST_F(MutexTest, NamedConstructor) {
std::string name = generate_unique_mutex_name("named_ctor"); std::string name = generate_unique_mutex_name("named_ctor");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
EXPECT_TRUE(mtx.valid()); EXPECT_TRUE(mtx.valid());
} }
// Test native() const method // Test native() const method
TEST_F(MutexTest, NativeConst) { TEST_F(MutexTest, NativeConst) {
std::string name = generate_unique_mutex_name("native_const"); std::string name = generate_unique_mutex_name("native_const");
const mutex mtx(name.c_str()); const mutex mtx(name.c_str());
const void* native_handle = mtx.native(); const void* native_handle = mtx.native();
EXPECT_NE(native_handle, nullptr); EXPECT_NE(native_handle, nullptr);
} }
// Test native() non-const method // Test native() non-const method
TEST_F(MutexTest, NativeNonConst) { TEST_F(MutexTest, NativeNonConst) {
std::string name = generate_unique_mutex_name("native_nonconst"); std::string name = generate_unique_mutex_name("native_nonconst");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
void* native_handle = mtx.native(); void* native_handle = mtx.native();
EXPECT_NE(native_handle, nullptr); EXPECT_NE(native_handle, nullptr);
} }
// Test valid() method // Test valid() method
TEST_F(MutexTest, Valid) { TEST_F(MutexTest, Valid) {
mutex mtx1; mutex mtx1;
// May or may not be valid without open // May or may not be valid without open
std::string name = generate_unique_mutex_name("valid"); std::string name = generate_unique_mutex_name("valid");
mutex mtx2(name.c_str()); mutex mtx2(name.c_str());
EXPECT_TRUE(mtx2.valid()); EXPECT_TRUE(mtx2.valid());
} }
// Test open() method // Test open() method
TEST_F(MutexTest, Open) { TEST_F(MutexTest, Open) {
std::string name = generate_unique_mutex_name("open"); std::string name = generate_unique_mutex_name("open");
mutex mtx; mutex mtx;
bool result = mtx.open(name.c_str()); bool result = mtx.open(name.c_str());
EXPECT_TRUE(result); EXPECT_TRUE(result);
EXPECT_TRUE(mtx.valid()); EXPECT_TRUE(mtx.valid());
} }
// Test close() method // Test close() method
TEST_F(MutexTest, Close) { TEST_F(MutexTest, Close) {
std::string name = generate_unique_mutex_name("close"); std::string name = generate_unique_mutex_name("close");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.close(); mtx.close();
EXPECT_FALSE(mtx.valid()); EXPECT_FALSE(mtx.valid());
} }
// Test clear() method // Test clear() method
TEST_F(MutexTest, Clear) { TEST_F(MutexTest, Clear) {
std::string name = generate_unique_mutex_name("clear"); std::string name = generate_unique_mutex_name("clear");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.clear(); mtx.clear();
EXPECT_FALSE(mtx.valid()); EXPECT_FALSE(mtx.valid());
} }
// Test clear_storage() static method // Test clear_storage() static method
TEST_F(MutexTest, ClearStorage) { TEST_F(MutexTest, ClearStorage) {
std::string name = generate_unique_mutex_name("clear_storage"); std::string name = generate_unique_mutex_name("clear_storage");
{ {
mutex mtx(name.c_str()); mutex mtx(name.c_str());
EXPECT_TRUE(mtx.valid()); EXPECT_TRUE(mtx.valid());
} }
mutex::clear_storage(name.c_str()); mutex::clear_storage(name.c_str());
// Try to open after clear - should create new or fail gracefully // Try to open after clear - should create new or fail gracefully
mutex mtx2(name.c_str()); mutex mtx2(name.c_str());
} }
// Test basic lock and unlock // Test basic lock and unlock
TEST_F(MutexTest, LockUnlock) { TEST_F(MutexTest, LockUnlock) {
std::string name = generate_unique_mutex_name("lock_unlock"); std::string name = generate_unique_mutex_name("lock_unlock");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
bool locked = mtx.lock(); bool locked = mtx.lock();
EXPECT_TRUE(locked); EXPECT_TRUE(locked);
bool unlocked = mtx.unlock(); bool unlocked = mtx.unlock();
EXPECT_TRUE(unlocked); EXPECT_TRUE(unlocked);
} }
// Test try_lock // Test try_lock
TEST_F(MutexTest, TryLock) { TEST_F(MutexTest, TryLock) {
std::string name = generate_unique_mutex_name("try_lock"); std::string name = generate_unique_mutex_name("try_lock");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
bool locked = mtx.try_lock(); bool locked = mtx.try_lock();
EXPECT_TRUE(locked); EXPECT_TRUE(locked);
if (locked) { if (locked) {
mtx.unlock(); mtx.unlock();
} }
} }
// Test timed lock with infinite timeout // Test timed lock with infinite timeout
TEST_F(MutexTest, TimedLockInfinite) { TEST_F(MutexTest, TimedLockInfinite) {
std::string name = generate_unique_mutex_name("timed_lock_inf"); std::string name = generate_unique_mutex_name("timed_lock_inf");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
bool locked = mtx.lock(invalid_value); bool locked = mtx.lock(invalid_value);
EXPECT_TRUE(locked); EXPECT_TRUE(locked);
if (locked) { if (locked) {
mtx.unlock(); mtx.unlock();
} }
} }
// Test timed lock with timeout // Test timed lock with timeout
TEST_F(MutexTest, TimedLockTimeout) { TEST_F(MutexTest, TimedLockTimeout) {
std::string name = generate_unique_mutex_name("timed_lock_timeout"); std::string name = generate_unique_mutex_name("timed_lock_timeout");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
// Lock with 100ms timeout // Lock with 100ms timeout
bool locked = mtx.lock(100); bool locked = mtx.lock(100);
EXPECT_TRUE(locked); EXPECT_TRUE(locked);
if (locked) { if (locked) {
mtx.unlock(); mtx.unlock();
} }
} }
// Test mutex protects critical section // Test mutex protects critical section
TEST_F(MutexTest, CriticalSection) { TEST_F(MutexTest, CriticalSection) {
std::string name = generate_unique_mutex_name("critical_section"); std::string name = generate_unique_mutex_name("critical_section");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
int shared_counter = 0; int shared_counter = 0;
const int iterations = 100; const int iterations = 100;
auto increment_task = [&]() { auto increment_task = [&]() {
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
mtx.lock(); mtx.lock();
++shared_counter; ++shared_counter;
mtx.unlock(); mtx.unlock();
} }
}; };
std::thread t1(increment_task); std::thread t1(increment_task);
std::thread t2(increment_task); std::thread t2(increment_task);
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(shared_counter, iterations * 2); EXPECT_EQ(shared_counter, iterations * 2);
} }
// Test concurrent try_lock // Test concurrent try_lock
TEST_F(MutexTest, ConcurrentTryLock) { TEST_F(MutexTest, ConcurrentTryLock) {
std::string name = generate_unique_mutex_name("concurrent_try"); std::string name = generate_unique_mutex_name("concurrent_try");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<int> success_count{0}; std::atomic<int> success_count{0};
std::atomic<int> fail_count{0}; std::atomic<int> fail_count{0};
auto try_lock_task = [&]() { auto try_lock_task = [&]() {
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
if (mtx.try_lock()) { if (mtx.try_lock()) {
++success_count; ++success_count;
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
mtx.unlock(); mtx.unlock();
} else { } else {
++fail_count; ++fail_count;
} }
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(try_lock_task); std::thread t1(try_lock_task);
std::thread t2(try_lock_task); std::thread t2(try_lock_task);
std::thread t3(try_lock_task); std::thread t3(try_lock_task);
t1.join(); t1.join();
t2.join(); t2.join();
t3.join(); t3.join();
EXPECT_GT(success_count.load(), 0); EXPECT_GT(success_count.load(), 0);
// Some try_locks should succeed // Some try_locks should succeed
} }
// Test lock contention // Test lock contention
TEST_F(MutexTest, LockContention) { TEST_F(MutexTest, LockContention) {
std::string name = generate_unique_mutex_name("contention"); std::string name = generate_unique_mutex_name("contention");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
std::atomic<bool> thread1_in_cs{false}; std::atomic<bool> thread1_in_cs{false};
std::atomic<bool> thread2_in_cs{false}; std::atomic<bool> thread2_in_cs{false};
std::atomic<bool> violation{false}; std::atomic<bool> violation{false};
auto contention_task = [&](std::atomic<bool>& my_flag, auto contention_task = [&](std::atomic<bool>& my_flag,
std::atomic<bool>& other_flag) { std::atomic<bool>& other_flag) {
for (int i = 0; i < 50; ++i) { for (int i = 0; i < 50; ++i) {
mtx.lock(); mtx.lock();
my_flag.store(true); my_flag.store(true);
if (other_flag.load()) { if (other_flag.load()) {
violation.store(true); violation.store(true);
} }
std::this_thread::sleep_for(std::chrono::microseconds(10)); std::this_thread::sleep_for(std::chrono::microseconds(10));
my_flag.store(false); my_flag.store(false);
mtx.unlock(); mtx.unlock();
std::this_thread::yield(); std::this_thread::yield();
} }
}; };
std::thread t1(contention_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs)); std::thread t1(contention_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs));
std::thread t2(contention_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs)); std::thread t2(contention_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs));
t1.join(); t1.join();
t2.join(); t2.join();
// Should never have both threads in critical section // Should never have both threads in critical section
EXPECT_FALSE(violation.load()); EXPECT_FALSE(violation.load());
} }
// Test multiple lock/unlock cycles // Test multiple lock/unlock cycles
TEST_F(MutexTest, MultipleCycles) { TEST_F(MutexTest, MultipleCycles) {
std::string name = generate_unique_mutex_name("cycles"); std::string name = generate_unique_mutex_name("cycles");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
ASSERT_TRUE(mtx.lock()); ASSERT_TRUE(mtx.lock());
ASSERT_TRUE(mtx.unlock()); ASSERT_TRUE(mtx.unlock());
} }
} }
// Test timed lock timeout scenario // Test timed lock timeout scenario
TEST_F(MutexTest, TimedLockTimeoutScenario) { TEST_F(MutexTest, TimedLockTimeoutScenario) {
std::string name = generate_unique_mutex_name("timeout_scenario"); std::string name = generate_unique_mutex_name("timeout_scenario");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
// Lock in main thread // Lock in main thread
ASSERT_TRUE(mtx.lock()); ASSERT_TRUE(mtx.lock());
std::atomic<bool> timeout_occurred{false}; std::atomic<bool> timeout_occurred{false};
std::thread t([&]() { std::thread t([&]() {
// Try to lock with short timeout - should timeout // Try to lock with short timeout - should timeout
bool locked = mtx.lock(50); // 50ms timeout bool locked = mtx.lock(50); // 50ms timeout
if (!locked) { if (!locked) {
timeout_occurred.store(true); timeout_occurred.store(true);
} else { } else {
mtx.unlock(); mtx.unlock();
} }
}); });
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.unlock(); mtx.unlock();
t.join(); t.join();
// Timeout should have occurred since we held the lock // Timeout should have occurred since we held the lock
EXPECT_TRUE(timeout_occurred.load()); EXPECT_TRUE(timeout_occurred.load());
} }
// Test reopen after close // Test reopen after close
TEST_F(MutexTest, ReopenAfterClose) { TEST_F(MutexTest, ReopenAfterClose) {
std::string name = generate_unique_mutex_name("reopen"); std::string name = generate_unique_mutex_name("reopen");
mutex mtx; mutex mtx;
ASSERT_TRUE(mtx.open(name.c_str())); ASSERT_TRUE(mtx.open(name.c_str()));
EXPECT_TRUE(mtx.valid()); EXPECT_TRUE(mtx.valid());
mtx.close(); mtx.close();
EXPECT_FALSE(mtx.valid()); EXPECT_FALSE(mtx.valid());
ASSERT_TRUE(mtx.open(name.c_str())); ASSERT_TRUE(mtx.open(name.c_str()));
EXPECT_TRUE(mtx.valid()); EXPECT_TRUE(mtx.valid());
} }
// Test named mutex inter-thread synchronization // Test named mutex inter-thread synchronization
TEST_F(MutexTest, NamedMutexInterThread) { TEST_F(MutexTest, NamedMutexInterThread) {
std::string name = generate_unique_mutex_name("inter_thread"); std::string name = generate_unique_mutex_name("inter_thread");
int shared_data = 0; int shared_data = 0;
std::atomic<bool> t1_done{false}; std::atomic<bool> t1_done{false};
std::atomic<bool> t2_done{false}; std::atomic<bool> t2_done{false};
std::thread t1([&]() { std::thread t1([&]() {
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.lock(); mtx.lock();
shared_data = 100; shared_data = 100;
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.unlock(); mtx.unlock();
t1_done.store(true); t1_done.store(true);
}); });
std::thread t2([&]() { std::thread t2([&]() {
// Wait a bit to ensure t1 starts first // Wait a bit to ensure t1 starts first
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.lock(); mtx.lock();
EXPECT_TRUE(t1_done.load() || shared_data == 100); EXPECT_TRUE(t1_done.load() || shared_data == 100);
shared_data = 200; shared_data = 200;
mtx.unlock(); mtx.unlock();
t2_done.store(true); t2_done.store(true);
}); });
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(shared_data, 200); EXPECT_EQ(shared_data, 200);
} }
// Test exception safety of try_lock // Test exception safety of try_lock
TEST_F(MutexTest, TryLockExceptionSafety) { TEST_F(MutexTest, TryLockExceptionSafety) {
std::string name = generate_unique_mutex_name("try_lock_exception"); std::string name = generate_unique_mutex_name("try_lock_exception");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
bool exception_thrown = false; bool exception_thrown = false;
try { try {
mtx.try_lock(); mtx.try_lock();
} catch (const std::system_error&) { } catch (const std::system_error&) {
exception_thrown = true; exception_thrown = true;
} catch (...) { } catch (...) {
FAIL() << "Unexpected exception type"; FAIL() << "Unexpected exception type";
} }
// try_lock may throw system_error // try_lock may throw system_error
// Just ensure we can handle it // Just ensure we can handle it
} }
// Test concurrent open/close operations // Test concurrent open/close operations
TEST_F(MutexTest, ConcurrentOpenClose) { TEST_F(MutexTest, ConcurrentOpenClose) {
std::vector<std::thread> threads; std::vector<std::thread> threads;
std::atomic<int> success_count{0}; std::atomic<int> success_count{0};
for (int i = 0; i < 5; ++i) { for (int i = 0; i < 5; ++i) {
threads.emplace_back([&, i]() { threads.emplace_back([&, i]() {
std::string name = generate_unique_mutex_name("concurrent"); std::string name = generate_unique_mutex_name("concurrent");
name += std::to_string(i); name += std::to_string(i);
mutex mtx; mutex mtx;
if (mtx.open(name.c_str())) { if (mtx.open(name.c_str())) {
++success_count; ++success_count;
mtx.close(); mtx.close();
} }
}); });
} }
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
EXPECT_EQ(success_count.load(), 5); EXPECT_EQ(success_count.load(), 5);
} }
// Test mutex with zero timeout // Test mutex with zero timeout
TEST_F(MutexTest, ZeroTimeout) { TEST_F(MutexTest, ZeroTimeout) {
std::string name = generate_unique_mutex_name("zero_timeout"); std::string name = generate_unique_mutex_name("zero_timeout");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
// Lock with zero timeout (should try once and return) // Lock with zero timeout (should try once and return)
bool locked = mtx.lock(0); bool locked = mtx.lock(0);
if (locked) { if (locked) {
mtx.unlock(); mtx.unlock();
} }
// Result may vary, just ensure it doesn't hang // Result may vary, just ensure it doesn't hang
} }
// Test rapid lock/unlock sequence // Test rapid lock/unlock sequence
TEST_F(MutexTest, RapidLockUnlock) { TEST_F(MutexTest, RapidLockUnlock) {
std::string name = generate_unique_mutex_name("rapid"); std::string name = generate_unique_mutex_name("rapid");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
auto rapid_task = [&]() { auto rapid_task = [&]() {
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
mtx.lock(); mtx.lock();
mtx.unlock(); mtx.unlock();
} }
}; };
std::thread t1(rapid_task); std::thread t1(rapid_task);
std::thread t2(rapid_task); std::thread t2(rapid_task);
t1.join(); t1.join();
t2.join(); t2.join();
// Should complete without deadlock or crash // Should complete without deadlock or crash
} }
// Test lock after clear // Test lock after clear
TEST_F(MutexTest, LockAfterClear) { TEST_F(MutexTest, LockAfterClear) {
std::string name = generate_unique_mutex_name("lock_after_clear"); std::string name = generate_unique_mutex_name("lock_after_clear");
mutex mtx(name.c_str()); mutex mtx(name.c_str());
ASSERT_TRUE(mtx.valid()); ASSERT_TRUE(mtx.valid());
mtx.lock(); mtx.lock();
mtx.unlock(); mtx.unlock();
mtx.clear(); mtx.clear();
EXPECT_FALSE(mtx.valid()); EXPECT_FALSE(mtx.valid());
// Attempting to lock after clear should fail gracefully // Attempting to lock after clear should fail gracefully
bool locked = mtx.lock(); bool locked = mtx.lock();
EXPECT_FALSE(locked); EXPECT_FALSE(locked);
} }

View File

@ -26,462 +26,462 @@ using namespace ipc::sync;
namespace { namespace {
std::string generate_unique_sem_name(const char* prefix) { std::string generate_unique_sem_name(const char* prefix) {
static int counter = 0; static int counter = 0;
return std::string(prefix) + "_sem_" + std::to_string(++counter); return std::string(prefix) + "_sem_" + std::to_string(++counter);
} }
} // anonymous namespace } // anonymous namespace
class SemaphoreTest : public ::testing::Test { class SemaphoreTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
}; };
// Test default constructor // Test default constructor
TEST_F(SemaphoreTest, DefaultConstructor) { TEST_F(SemaphoreTest, DefaultConstructor) {
semaphore sem; semaphore sem;
// Default constructed semaphore // Default constructed semaphore
} }
// Test named constructor with count // Test named constructor with count
TEST_F(SemaphoreTest, NamedConstructorWithCount) { TEST_F(SemaphoreTest, NamedConstructorWithCount) {
std::string name = generate_unique_sem_name("named_count"); std::string name = generate_unique_sem_name("named_count");
semaphore sem(name.c_str(), 5); semaphore sem(name.c_str(), 5);
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
} }
// Test named constructor with zero count // Test named constructor with zero count
TEST_F(SemaphoreTest, NamedConstructorZeroCount) { TEST_F(SemaphoreTest, NamedConstructorZeroCount) {
std::string name = generate_unique_sem_name("zero_count"); std::string name = generate_unique_sem_name("zero_count");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
} }
// Test native() methods // Test native() methods
TEST_F(SemaphoreTest, NativeHandle) { TEST_F(SemaphoreTest, NativeHandle) {
std::string name = generate_unique_sem_name("native"); std::string name = generate_unique_sem_name("native");
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
const void* const_handle = static_cast<const semaphore&>(sem).native(); const void* const_handle = static_cast<const semaphore&>(sem).native();
void* handle = sem.native(); void* handle = sem.native();
EXPECT_NE(const_handle, nullptr); EXPECT_NE(const_handle, nullptr);
EXPECT_NE(handle, nullptr); EXPECT_NE(handle, nullptr);
} }
// Test valid() method // Test valid() method
TEST_F(SemaphoreTest, Valid) { TEST_F(SemaphoreTest, Valid) {
semaphore sem1; semaphore sem1;
std::string name = generate_unique_sem_name("valid"); std::string name = generate_unique_sem_name("valid");
semaphore sem2(name.c_str(), 1); semaphore sem2(name.c_str(), 1);
EXPECT_TRUE(sem2.valid()); EXPECT_TRUE(sem2.valid());
} }
// Test open() method // Test open() method
TEST_F(SemaphoreTest, Open) { TEST_F(SemaphoreTest, Open) {
std::string name = generate_unique_sem_name("open"); std::string name = generate_unique_sem_name("open");
semaphore sem; semaphore sem;
bool result = sem.open(name.c_str(), 3); bool result = sem.open(name.c_str(), 3);
EXPECT_TRUE(result); EXPECT_TRUE(result);
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
} }
// Test close() method // Test close() method
TEST_F(SemaphoreTest, Close) { TEST_F(SemaphoreTest, Close) {
std::string name = generate_unique_sem_name("close"); std::string name = generate_unique_sem_name("close");
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
sem.close(); sem.close();
EXPECT_FALSE(sem.valid()); EXPECT_FALSE(sem.valid());
} }
// Test clear() method // Test clear() method
TEST_F(SemaphoreTest, Clear) { TEST_F(SemaphoreTest, Clear) {
std::string name = generate_unique_sem_name("clear"); std::string name = generate_unique_sem_name("clear");
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
sem.clear(); sem.clear();
EXPECT_FALSE(sem.valid()); EXPECT_FALSE(sem.valid());
} }
// Test clear_storage() static method // Test clear_storage() static method
TEST_F(SemaphoreTest, ClearStorage) { TEST_F(SemaphoreTest, ClearStorage) {
std::string name = generate_unique_sem_name("clear_storage"); std::string name = generate_unique_sem_name("clear_storage");
{ {
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
} }
semaphore::clear_storage(name.c_str()); semaphore::clear_storage(name.c_str());
} }
// Test basic wait and post // Test basic wait and post
TEST_F(SemaphoreTest, WaitPost) { TEST_F(SemaphoreTest, WaitPost) {
std::string name = generate_unique_sem_name("wait_post"); std::string name = generate_unique_sem_name("wait_post");
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
bool waited = sem.wait(); bool waited = sem.wait();
EXPECT_TRUE(waited); EXPECT_TRUE(waited);
bool posted = sem.post(); bool posted = sem.post();
EXPECT_TRUE(posted); EXPECT_TRUE(posted);
} }
// Test post with count // Test post with count
TEST_F(SemaphoreTest, PostWithCount) { TEST_F(SemaphoreTest, PostWithCount) {
std::string name = generate_unique_sem_name("post_count"); std::string name = generate_unique_sem_name("post_count");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
bool posted = sem.post(5); bool posted = sem.post(5);
EXPECT_TRUE(posted); EXPECT_TRUE(posted);
// Now should be able to wait 5 times // Now should be able to wait 5 times
for (int i = 0; i < 5; ++i) { for (int i = 0; i < 5; ++i) {
EXPECT_TRUE(sem.wait(10)); // 10ms timeout EXPECT_TRUE(sem.wait(10)); // 10ms timeout
} }
} }
// Test timed wait with timeout // Test timed wait with timeout
TEST_F(SemaphoreTest, TimedWait) { TEST_F(SemaphoreTest, TimedWait) {
std::string name = generate_unique_sem_name("timed_wait"); std::string name = generate_unique_sem_name("timed_wait");
semaphore sem(name.c_str(), 1); semaphore sem(name.c_str(), 1);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
bool waited = sem.wait(100); // 100ms timeout bool waited = sem.wait(100); // 100ms timeout
EXPECT_TRUE(waited); EXPECT_TRUE(waited);
} }
// Test wait timeout scenario // Test wait timeout scenario
TEST_F(SemaphoreTest, WaitTimeout) { TEST_F(SemaphoreTest, WaitTimeout) {
std::string name = generate_unique_sem_name("wait_timeout"); std::string name = generate_unique_sem_name("wait_timeout");
semaphore sem(name.c_str(), 0); // Zero count semaphore sem(name.c_str(), 0); // Zero count
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
bool waited = sem.wait(50); // 50ms timeout bool waited = sem.wait(50); // 50ms timeout
auto end = std::chrono::steady_clock::now(); auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
// Should timeout // Should timeout
EXPECT_FALSE(waited); EXPECT_FALSE(waited);
EXPECT_GE(elapsed, 40); // Allow some tolerance EXPECT_GE(elapsed, 40); // Allow some tolerance
} }
// Test infinite wait // Test infinite wait
TEST_F(SemaphoreTest, InfiniteWait) { TEST_F(SemaphoreTest, InfiniteWait) {
std::string name = generate_unique_sem_name("infinite_wait"); std::string name = generate_unique_sem_name("infinite_wait");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
std::atomic<bool> wait_started{false}; std::atomic<bool> wait_started{false};
std::atomic<bool> wait_succeeded{false}; std::atomic<bool> wait_succeeded{false};
std::thread waiter([&]() { std::thread waiter([&]() {
wait_started.store(true); wait_started.store(true);
bool result = sem.wait(invalid_value); bool result = sem.wait(invalid_value);
wait_succeeded.store(result); wait_succeeded.store(result);
}); });
// Wait for thread to start waiting // Wait for thread to start waiting
while (!wait_started.load()) { while (!wait_started.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
// Post to release the waiter // Post to release the waiter
sem.post(); sem.post();
waiter.join(); waiter.join();
EXPECT_TRUE(wait_succeeded.load()); EXPECT_TRUE(wait_succeeded.load());
} }
// Test producer-consumer pattern // Test producer-consumer pattern
TEST_F(SemaphoreTest, ProducerConsumer) { TEST_F(SemaphoreTest, ProducerConsumer) {
std::string name = generate_unique_sem_name("prod_cons"); std::string name = generate_unique_sem_name("prod_cons");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
std::atomic<int> produced{0}; std::atomic<int> produced{0};
std::atomic<int> consumed{0}; std::atomic<int> consumed{0};
const int count = 10; const int count = 10;
std::thread producer([&]() { std::thread producer([&]() {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
++produced; ++produced;
sem.post(); sem.post();
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
}); });
std::thread consumer([&]() { std::thread consumer([&]() {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
sem.wait(); sem.wait();
++consumed; ++consumed;
} }
}); });
producer.join(); producer.join();
consumer.join(); consumer.join();
EXPECT_EQ(produced.load(), count); EXPECT_EQ(produced.load(), count);
EXPECT_EQ(consumed.load(), count); EXPECT_EQ(consumed.load(), count);
} }
// Test multiple producers and consumers // Test multiple producers and consumers
TEST_F(SemaphoreTest, MultipleProducersConsumers) { TEST_F(SemaphoreTest, MultipleProducersConsumers) {
std::string name = generate_unique_sem_name("multi_prod_cons"); std::string name = generate_unique_sem_name("multi_prod_cons");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
std::atomic<int> total_produced{0}; std::atomic<int> total_produced{0};
std::atomic<int> total_consumed{0}; std::atomic<int> total_consumed{0};
const int items_per_producer = 5; const int items_per_producer = 5;
const int num_producers = 3; const int num_producers = 3;
const int num_consumers = 3; const int num_consumers = 3;
std::vector<std::thread> producers; std::vector<std::thread> producers;
for (int i = 0; i < num_producers; ++i) { for (int i = 0; i < num_producers; ++i) {
producers.emplace_back([&]() { producers.emplace_back([&]() {
for (int j = 0; j < items_per_producer; ++j) { for (int j = 0; j < items_per_producer; ++j) {
++total_produced; ++total_produced;
sem.post(); sem.post();
std::this_thread::yield(); std::this_thread::yield();
} }
}); });
} }
std::vector<std::thread> consumers; std::vector<std::thread> consumers;
for (int i = 0; i < num_consumers; ++i) { for (int i = 0; i < num_consumers; ++i) {
consumers.emplace_back([&]() { consumers.emplace_back([&]() {
for (int j = 0; j < items_per_producer; ++j) { for (int j = 0; j < items_per_producer; ++j) {
if (sem.wait(1000)) { if (sem.wait(1000)) {
++total_consumed; ++total_consumed;
} }
} }
}); });
} }
for (auto& t : producers) t.join(); for (auto& t : producers) t.join();
for (auto& t : consumers) t.join(); for (auto& t : consumers) t.join();
EXPECT_EQ(total_produced.load(), items_per_producer * num_producers); EXPECT_EQ(total_produced.load(), items_per_producer * num_producers);
EXPECT_EQ(total_consumed.load(), items_per_producer * num_producers); EXPECT_EQ(total_consumed.load(), items_per_producer * num_producers);
} }
// Test semaphore with initial count // Test semaphore with initial count
TEST_F(SemaphoreTest, InitialCount) { TEST_F(SemaphoreTest, InitialCount) {
std::string name = generate_unique_sem_name("initial_count"); std::string name = generate_unique_sem_name("initial_count");
const uint32_t initial = 3; const uint32_t initial = 3;
semaphore sem(name.c_str(), initial); semaphore sem(name.c_str(), initial);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
// Should be able to wait 'initial' times without blocking // Should be able to wait 'initial' times without blocking
for (uint32_t i = 0; i < initial; ++i) { for (uint32_t i = 0; i < initial; ++i) {
EXPECT_TRUE(sem.wait(10)); EXPECT_TRUE(sem.wait(10));
} }
// Next wait should timeout // Next wait should timeout
EXPECT_FALSE(sem.wait(10)); EXPECT_FALSE(sem.wait(10));
} }
// Test rapid post operations // Test rapid post operations
TEST_F(SemaphoreTest, RapidPost) { TEST_F(SemaphoreTest, RapidPost) {
std::string name = generate_unique_sem_name("rapid_post"); std::string name = generate_unique_sem_name("rapid_post");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
const int post_count = 100; const int post_count = 100;
for (int i = 0; i < post_count; ++i) { for (int i = 0; i < post_count; ++i) {
EXPECT_TRUE(sem.post()); EXPECT_TRUE(sem.post());
} }
// Should be able to wait post_count times // Should be able to wait post_count times
int wait_count = 0; int wait_count = 0;
for (int i = 0; i < post_count; ++i) { for (int i = 0; i < post_count; ++i) {
if (sem.wait(10)) { if (sem.wait(10)) {
++wait_count; ++wait_count;
} }
} }
EXPECT_EQ(wait_count, post_count); EXPECT_EQ(wait_count, post_count);
} }
// Test concurrent post operations // Test concurrent post operations
TEST_F(SemaphoreTest, ConcurrentPost) { TEST_F(SemaphoreTest, ConcurrentPost) {
std::string name = generate_unique_sem_name("concurrent_post"); std::string name = generate_unique_sem_name("concurrent_post");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
std::atomic<int> post_count{0}; std::atomic<int> post_count{0};
const int threads = 5; const int threads = 5;
const int posts_per_thread = 10; const int posts_per_thread = 10;
std::vector<std::thread> posters; std::vector<std::thread> posters;
for (int i = 0; i < threads; ++i) { for (int i = 0; i < threads; ++i) {
posters.emplace_back([&]() { posters.emplace_back([&]() {
for (int j = 0; j < posts_per_thread; ++j) { for (int j = 0; j < posts_per_thread; ++j) {
if (sem.post()) { if (sem.post()) {
++post_count; ++post_count;
} }
} }
}); });
} }
for (auto& t : posters) t.join(); for (auto& t : posters) t.join();
EXPECT_EQ(post_count.load(), threads * posts_per_thread); EXPECT_EQ(post_count.load(), threads * posts_per_thread);
// Verify by consuming // Verify by consuming
int consumed = 0; int consumed = 0;
for (int i = 0; i < threads * posts_per_thread; ++i) { for (int i = 0; i < threads * posts_per_thread; ++i) {
if (sem.wait(10)) { if (sem.wait(10)) {
++consumed; ++consumed;
} }
} }
EXPECT_EQ(consumed, threads * posts_per_thread); EXPECT_EQ(consumed, threads * posts_per_thread);
} }
// Test reopen after close // Test reopen after close
TEST_F(SemaphoreTest, ReopenAfterClose) { TEST_F(SemaphoreTest, ReopenAfterClose) {
std::string name = generate_unique_sem_name("reopen"); std::string name = generate_unique_sem_name("reopen");
semaphore sem; semaphore sem;
ASSERT_TRUE(sem.open(name.c_str(), 2)); ASSERT_TRUE(sem.open(name.c_str(), 2));
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
sem.close(); sem.close();
EXPECT_FALSE(sem.valid()); EXPECT_FALSE(sem.valid());
ASSERT_TRUE(sem.open(name.c_str(), 3)); ASSERT_TRUE(sem.open(name.c_str(), 3));
EXPECT_TRUE(sem.valid()); EXPECT_TRUE(sem.valid());
} }
// Test named semaphore sharing between threads // Test named semaphore sharing between threads
TEST_F(SemaphoreTest, NamedSemaphoreSharing) { TEST_F(SemaphoreTest, NamedSemaphoreSharing) {
std::string name = generate_unique_sem_name("sharing"); std::string name = generate_unique_sem_name("sharing");
std::atomic<int> value{0}; std::atomic<int> value{0};
std::thread t1([&]() { std::thread t1([&]() {
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
sem.wait(); // Wait for signal sem.wait(); // Wait for signal
value.store(100); value.store(100);
}); });
std::thread t2([&]() { std::thread t2([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
sem.post(); // Signal t1 sem.post(); // Signal t1
}); });
t1.join(); t1.join();
t2.join(); t2.join();
EXPECT_EQ(value.load(), 100); EXPECT_EQ(value.load(), 100);
} }
// Test post multiple count at once // Test post multiple count at once
TEST_F(SemaphoreTest, PostMultiple) { TEST_F(SemaphoreTest, PostMultiple) {
std::string name = generate_unique_sem_name("post_multiple"); std::string name = generate_unique_sem_name("post_multiple");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
const uint32_t count = 10; const uint32_t count = 10;
bool posted = sem.post(count); bool posted = sem.post(count);
EXPECT_TRUE(posted); EXPECT_TRUE(posted);
// Consume all // Consume all
for (uint32_t i = 0; i < count; ++i) { for (uint32_t i = 0; i < count; ++i) {
EXPECT_TRUE(sem.wait(10)); EXPECT_TRUE(sem.wait(10));
} }
// Should be empty now // Should be empty now
EXPECT_FALSE(sem.wait(10)); EXPECT_FALSE(sem.wait(10));
} }
// Test semaphore after clear // Test semaphore after clear
TEST_F(SemaphoreTest, AfterClear) { TEST_F(SemaphoreTest, AfterClear) {
std::string name = generate_unique_sem_name("after_clear"); std::string name = generate_unique_sem_name("after_clear");
semaphore sem(name.c_str(), 5); semaphore sem(name.c_str(), 5);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
sem.wait(); sem.wait();
sem.clear(); sem.clear();
EXPECT_FALSE(sem.valid()); EXPECT_FALSE(sem.valid());
// Operations after clear should fail gracefully // Operations after clear should fail gracefully
EXPECT_FALSE(sem.wait(10)); EXPECT_FALSE(sem.wait(10));
EXPECT_FALSE(sem.post()); EXPECT_FALSE(sem.post());
} }
// Test zero timeout // Test zero timeout
TEST_F(SemaphoreTest, ZeroTimeout) { TEST_F(SemaphoreTest, ZeroTimeout) {
std::string name = generate_unique_sem_name("zero_timeout"); std::string name = generate_unique_sem_name("zero_timeout");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
bool waited = sem.wait(0); bool waited = sem.wait(0);
// Should return immediately (either success or timeout) // Should return immediately (either success or timeout)
} }
// Test high-frequency wait/post // Test high-frequency wait/post
TEST_F(SemaphoreTest, HighFrequency) { TEST_F(SemaphoreTest, HighFrequency) {
std::string name = generate_unique_sem_name("high_freq"); std::string name = generate_unique_sem_name("high_freq");
semaphore sem(name.c_str(), 0); semaphore sem(name.c_str(), 0);
ASSERT_TRUE(sem.valid()); ASSERT_TRUE(sem.valid());
std::thread poster([&]() { std::thread poster([&]() {
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
sem.post(); sem.post();
} }
}); });
std::thread waiter([&]() { std::thread waiter([&]() {
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
sem.wait(100); sem.wait(100);
} }
}); });
poster.join(); poster.join();
waiter.join(); waiter.join();
} }

View File

@ -23,477 +23,477 @@ namespace {
// Generate unique shared memory names for tests // Generate unique shared memory names for tests
std::string generate_unique_name(const char* prefix) { std::string generate_unique_name(const char* prefix) {
static int counter = 0; static int counter = 0;
return std::string(prefix) + "_test_" + std::to_string(++counter); return std::string(prefix) + "_test_" + std::to_string(++counter);
} }
} // anonymous namespace } // anonymous namespace
class ShmTest : public ::testing::Test { class ShmTest : public ::testing::Test {
protected: protected:
void TearDown() override { void TearDown() override {
// Clean up any leftover shared memory segments // Clean up any leftover shared memory segments
} }
}; };
// ========== Low-level API Tests ========== // ========== Low-level API Tests ==========
// Test acquire with create mode // Test acquire with create mode
TEST_F(ShmTest, AcquireCreate) { TEST_F(ShmTest, AcquireCreate) {
std::string name = generate_unique_name("acquire_create"); std::string name = generate_unique_name("acquire_create");
const std::size_t size = 1024; const std::size_t size = 1024;
id_t id = acquire(name.c_str(), size, create); id_t id = acquire(name.c_str(), size, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
std::size_t actual_size = 0; std::size_t actual_size = 0;
void* mem = get_mem(id, &actual_size); void* mem = get_mem(id, &actual_size);
EXPECT_NE(mem, nullptr); EXPECT_NE(mem, nullptr);
EXPECT_GE(actual_size, size); EXPECT_GE(actual_size, size);
release(id); release(id);
remove(id); remove(id);
} }
// Test acquire with open mode (should fail if not exists) // Test acquire with open mode (should fail if not exists)
TEST_F(ShmTest, AcquireOpenNonExistent) { TEST_F(ShmTest, AcquireOpenNonExistent) {
std::string name = generate_unique_name("acquire_open_fail"); std::string name = generate_unique_name("acquire_open_fail");
id_t id = acquire(name.c_str(), 1024, open); id_t id = acquire(name.c_str(), 1024, open);
// Opening non-existent shared memory should return nullptr or handle failure gracefully // Opening non-existent shared memory should return nullptr or handle failure gracefully
if (id != nullptr) { if (id != nullptr) {
release(id); release(id);
} }
} }
// Test acquire with both create and open modes // Test acquire with both create and open modes
TEST_F(ShmTest, AcquireCreateOrOpen) { TEST_F(ShmTest, AcquireCreateOrOpen) {
std::string name = generate_unique_name("acquire_both"); std::string name = generate_unique_name("acquire_both");
const std::size_t size = 2048; const std::size_t size = 2048;
id_t id = acquire(name.c_str(), size, create | open); id_t id = acquire(name.c_str(), size, create | open);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
std::size_t actual_size = 0; std::size_t actual_size = 0;
void* mem = get_mem(id, &actual_size); void* mem = get_mem(id, &actual_size);
EXPECT_NE(mem, nullptr); EXPECT_NE(mem, nullptr);
EXPECT_GE(actual_size, size); EXPECT_GE(actual_size, size);
release(id); release(id);
remove(id); remove(id);
} }
// Test get_mem function // Test get_mem function
TEST_F(ShmTest, GetMemory) { TEST_F(ShmTest, GetMemory) {
std::string name = generate_unique_name("get_mem"); std::string name = generate_unique_name("get_mem");
const std::size_t size = 512; const std::size_t size = 512;
id_t id = acquire(name.c_str(), size, create); id_t id = acquire(name.c_str(), size, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
std::size_t returned_size = 0; std::size_t returned_size = 0;
void* mem = get_mem(id, &returned_size); void* mem = get_mem(id, &returned_size);
EXPECT_NE(mem, nullptr); EXPECT_NE(mem, nullptr);
EXPECT_GE(returned_size, size); EXPECT_GE(returned_size, size);
// Write and read test data // Write and read test data
const char* test_data = "Shared memory test data"; const char* test_data = "Shared memory test data";
std::strcpy(static_cast<char*>(mem), test_data); std::strcpy(static_cast<char*>(mem), test_data);
EXPECT_STREQ(static_cast<char*>(mem), test_data); EXPECT_STREQ(static_cast<char*>(mem), test_data);
release(id); release(id);
remove(id); remove(id);
} }
// Test get_mem without size parameter // Test get_mem without size parameter
TEST_F(ShmTest, GetMemoryNoSize) { TEST_F(ShmTest, GetMemoryNoSize) {
std::string name = generate_unique_name("get_mem_no_size"); std::string name = generate_unique_name("get_mem_no_size");
id_t id = acquire(name.c_str(), 256, create); id_t id = acquire(name.c_str(), 256, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
void* mem = get_mem(id, nullptr); void* mem = get_mem(id, nullptr);
EXPECT_NE(mem, nullptr); EXPECT_NE(mem, nullptr);
release(id); release(id);
remove(id); remove(id);
} }
// Test release function // Test release function
TEST_F(ShmTest, ReleaseMemory) { TEST_F(ShmTest, ReleaseMemory) {
std::string name = generate_unique_name("release"); std::string name = generate_unique_name("release");
id_t id = acquire(name.c_str(), 128, create); id_t id = acquire(name.c_str(), 128, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
std::int32_t ref_count = release(id); std::int32_t ref_count = release(id);
EXPECT_GE(ref_count, 0); EXPECT_GE(ref_count, 0);
remove(name.c_str()); remove(name.c_str());
} }
// Test remove by id // Test remove by id
TEST_F(ShmTest, RemoveById) { TEST_F(ShmTest, RemoveById) {
std::string name = generate_unique_name("remove_by_id"); std::string name = generate_unique_name("remove_by_id");
id_t id = acquire(name.c_str(), 256, create); id_t id = acquire(name.c_str(), 256, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
release(id); release(id);
remove(id); // Should succeed remove(id); // Should succeed
} }
// Test remove by name // Test remove by name
TEST_F(ShmTest, RemoveByName) { TEST_F(ShmTest, RemoveByName) {
std::string name = generate_unique_name("remove_by_name"); std::string name = generate_unique_name("remove_by_name");
id_t id = acquire(name.c_str(), 256, create); id_t id = acquire(name.c_str(), 256, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
release(id); release(id);
remove(name.c_str()); // Should succeed remove(name.c_str()); // Should succeed
} }
// Test reference counting // Test reference counting
TEST_F(ShmTest, ReferenceCount) { TEST_F(ShmTest, ReferenceCount) {
std::string name = generate_unique_name("ref_count"); std::string name = generate_unique_name("ref_count");
id_t id1 = acquire(name.c_str(), 512, create); id_t id1 = acquire(name.c_str(), 512, create);
ASSERT_NE(id1, nullptr); ASSERT_NE(id1, nullptr);
std::int32_t ref1 = get_ref(id1); std::int32_t ref1 = get_ref(id1);
EXPECT_GT(ref1, 0); EXPECT_GT(ref1, 0);
// Acquire again (should increase reference count) // Acquire again (should increase reference count)
id_t id2 = acquire(name.c_str(), 512, open); id_t id2 = acquire(name.c_str(), 512, open);
if (id2 != nullptr) { if (id2 != nullptr) {
std::int32_t ref2 = get_ref(id2); std::int32_t ref2 = get_ref(id2);
EXPECT_GE(ref2, ref1); EXPECT_GE(ref2, ref1);
release(id2); release(id2);
} }
release(id1); release(id1);
remove(name.c_str()); remove(name.c_str());
} }
// Test sub_ref function // Test sub_ref function
TEST_F(ShmTest, SubtractReference) { TEST_F(ShmTest, SubtractReference) {
std::string name = generate_unique_name("sub_ref"); std::string name = generate_unique_name("sub_ref");
id_t id = acquire(name.c_str(), 256, create); id_t id = acquire(name.c_str(), 256, create);
ASSERT_NE(id, nullptr); ASSERT_NE(id, nullptr);
std::int32_t ref_before = get_ref(id); std::int32_t ref_before = get_ref(id);
sub_ref(id); sub_ref(id);
std::int32_t ref_after = get_ref(id); std::int32_t ref_after = get_ref(id);
EXPECT_EQ(ref_after, ref_before - 1); EXPECT_EQ(ref_after, ref_before - 1);
release(id); release(id);
remove(id); remove(id);
} }
// ========== High-level handle class Tests ========== // ========== High-level handle class Tests ==========
// Test default handle constructor // Test default handle constructor
TEST_F(ShmTest, HandleDefaultConstructor) { TEST_F(ShmTest, HandleDefaultConstructor) {
handle h; handle h;
EXPECT_FALSE(h.valid()); EXPECT_FALSE(h.valid());
EXPECT_EQ(h.size(), 0u); EXPECT_EQ(h.size(), 0u);
EXPECT_EQ(h.get(), nullptr); EXPECT_EQ(h.get(), nullptr);
} }
// Test handle constructor with name and size // Test handle constructor with name and size
TEST_F(ShmTest, HandleConstructorWithParams) { TEST_F(ShmTest, HandleConstructorWithParams) {
std::string name = generate_unique_name("handle_ctor"); std::string name = generate_unique_name("handle_ctor");
const std::size_t size = 1024; const std::size_t size = 1024;
handle h(name.c_str(), size); handle h(name.c_str(), size);
EXPECT_TRUE(h.valid()); EXPECT_TRUE(h.valid());
EXPECT_GE(h.size(), size); EXPECT_GE(h.size(), size);
EXPECT_NE(h.get(), nullptr); EXPECT_NE(h.get(), nullptr);
EXPECT_STREQ(h.name(), name.c_str()); EXPECT_STREQ(h.name(), name.c_str());
} }
// Test handle move constructor // Test handle move constructor
TEST_F(ShmTest, HandleMoveConstructor) { TEST_F(ShmTest, HandleMoveConstructor) {
std::string name = generate_unique_name("handle_move"); std::string name = generate_unique_name("handle_move");
handle h1(name.c_str(), 512); handle h1(name.c_str(), 512);
ASSERT_TRUE(h1.valid()); ASSERT_TRUE(h1.valid());
void* ptr1 = h1.get(); void* ptr1 = h1.get();
std::size_t size1 = h1.size(); std::size_t size1 = h1.size();
handle h2(std::move(h1)); handle h2(std::move(h1));
EXPECT_TRUE(h2.valid()); EXPECT_TRUE(h2.valid());
EXPECT_EQ(h2.get(), ptr1); EXPECT_EQ(h2.get(), ptr1);
EXPECT_EQ(h2.size(), size1); EXPECT_EQ(h2.size(), size1);
// h1 should be invalid after move // h1 should be invalid after move
EXPECT_FALSE(h1.valid()); EXPECT_FALSE(h1.valid());
} }
// Test handle swap // Test handle swap
TEST_F(ShmTest, HandleSwap) { TEST_F(ShmTest, HandleSwap) {
std::string name1 = generate_unique_name("handle_swap1"); std::string name1 = generate_unique_name("handle_swap1");
std::string name2 = generate_unique_name("handle_swap2"); std::string name2 = generate_unique_name("handle_swap2");
handle h1(name1.c_str(), 256); handle h1(name1.c_str(), 256);
handle h2(name2.c_str(), 512); handle h2(name2.c_str(), 512);
void* ptr1 = h1.get(); void* ptr1 = h1.get();
void* ptr2 = h2.get(); void* ptr2 = h2.get();
std::size_t size1 = h1.size(); std::size_t size1 = h1.size();
std::size_t size2 = h2.size(); std::size_t size2 = h2.size();
h1.swap(h2); h1.swap(h2);
EXPECT_EQ(h1.get(), ptr2); EXPECT_EQ(h1.get(), ptr2);
EXPECT_EQ(h1.size(), size2); EXPECT_EQ(h1.size(), size2);
EXPECT_EQ(h2.get(), ptr1); EXPECT_EQ(h2.get(), ptr1);
EXPECT_EQ(h2.size(), size1); EXPECT_EQ(h2.size(), size1);
} }
// Test handle assignment operator // Test handle assignment operator
TEST_F(ShmTest, HandleAssignment) { TEST_F(ShmTest, HandleAssignment) {
std::string name = generate_unique_name("handle_assign"); std::string name = generate_unique_name("handle_assign");
handle h1(name.c_str(), 768); handle h1(name.c_str(), 768);
void* ptr1 = h1.get(); void* ptr1 = h1.get();
handle h2; handle h2;
h2 = std::move(h1); h2 = std::move(h1);
EXPECT_TRUE(h2.valid()); EXPECT_TRUE(h2.valid());
EXPECT_EQ(h2.get(), ptr1); EXPECT_EQ(h2.get(), ptr1);
EXPECT_FALSE(h1.valid()); EXPECT_FALSE(h1.valid());
} }
// Test handle valid() method // Test handle valid() method
TEST_F(ShmTest, HandleValid) { TEST_F(ShmTest, HandleValid) {
handle h1; handle h1;
EXPECT_FALSE(h1.valid()); EXPECT_FALSE(h1.valid());
std::string name = generate_unique_name("handle_valid"); std::string name = generate_unique_name("handle_valid");
handle h2(name.c_str(), 128); handle h2(name.c_str(), 128);
EXPECT_TRUE(h2.valid()); EXPECT_TRUE(h2.valid());
} }
// Test handle size() method // Test handle size() method
TEST_F(ShmTest, HandleSize) { TEST_F(ShmTest, HandleSize) {
std::string name = generate_unique_name("handle_size"); std::string name = generate_unique_name("handle_size");
const std::size_t requested_size = 2048; const std::size_t requested_size = 2048;
handle h(name.c_str(), requested_size); handle h(name.c_str(), requested_size);
EXPECT_GE(h.size(), requested_size); EXPECT_GE(h.size(), requested_size);
} }
// Test handle name() method // Test handle name() method
TEST_F(ShmTest, HandleName) { TEST_F(ShmTest, HandleName) {
std::string name = generate_unique_name("handle_name"); std::string name = generate_unique_name("handle_name");
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
EXPECT_STREQ(h.name(), name.c_str()); EXPECT_STREQ(h.name(), name.c_str());
} }
// Test handle ref() method // Test handle ref() method
TEST_F(ShmTest, HandleRef) { TEST_F(ShmTest, HandleRef) {
std::string name = generate_unique_name("handle_ref"); std::string name = generate_unique_name("handle_ref");
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
std::int32_t ref = h.ref(); std::int32_t ref = h.ref();
EXPECT_GT(ref, 0); EXPECT_GT(ref, 0);
} }
// Test handle sub_ref() method // Test handle sub_ref() method
TEST_F(ShmTest, HandleSubRef) { TEST_F(ShmTest, HandleSubRef) {
std::string name = generate_unique_name("handle_sub_ref"); std::string name = generate_unique_name("handle_sub_ref");
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
std::int32_t ref_before = h.ref(); std::int32_t ref_before = h.ref();
h.sub_ref(); h.sub_ref();
std::int32_t ref_after = h.ref(); std::int32_t ref_after = h.ref();
EXPECT_EQ(ref_after, ref_before - 1); EXPECT_EQ(ref_after, ref_before - 1);
} }
// Test handle acquire() method // Test handle acquire() method
TEST_F(ShmTest, HandleAcquire) { TEST_F(ShmTest, HandleAcquire) {
handle h; handle h;
EXPECT_FALSE(h.valid()); EXPECT_FALSE(h.valid());
std::string name = generate_unique_name("handle_acquire"); std::string name = generate_unique_name("handle_acquire");
bool result = h.acquire(name.c_str(), 512); bool result = h.acquire(name.c_str(), 512);
EXPECT_TRUE(result); EXPECT_TRUE(result);
EXPECT_TRUE(h.valid()); EXPECT_TRUE(h.valid());
EXPECT_GE(h.size(), 512u); EXPECT_GE(h.size(), 512u);
} }
// Test handle release() method // Test handle release() method
TEST_F(ShmTest, HandleRelease) { TEST_F(ShmTest, HandleRelease) {
std::string name = generate_unique_name("handle_release"); std::string name = generate_unique_name("handle_release");
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
ASSERT_TRUE(h.valid()); ASSERT_TRUE(h.valid());
std::int32_t ref_count = h.release(); std::int32_t ref_count = h.release();
EXPECT_GE(ref_count, 0); EXPECT_GE(ref_count, 0);
} }
// Test handle clear() method // Test handle clear() method
TEST_F(ShmTest, HandleClear) { TEST_F(ShmTest, HandleClear) {
std::string name = generate_unique_name("handle_clear"); std::string name = generate_unique_name("handle_clear");
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
ASSERT_TRUE(h.valid()); ASSERT_TRUE(h.valid());
h.clear(); h.clear();
EXPECT_FALSE(h.valid()); EXPECT_FALSE(h.valid());
} }
// Test handle clear_storage() static method // Test handle clear_storage() static method
TEST_F(ShmTest, HandleClearStorage) { TEST_F(ShmTest, HandleClearStorage) {
std::string name = generate_unique_name("handle_clear_storage"); std::string name = generate_unique_name("handle_clear_storage");
{ {
handle h(name.c_str(), 256); handle h(name.c_str(), 256);
EXPECT_TRUE(h.valid()); EXPECT_TRUE(h.valid());
} }
handle::clear_storage(name.c_str()); handle::clear_storage(name.c_str());
// Try to open - should fail or create new // Try to open - should fail or create new
handle h2(name.c_str(), 256, open); handle h2(name.c_str(), 256, open);
// Behavior depends on implementation // Behavior depends on implementation
} }
// Test handle get() method // Test handle get() method
TEST_F(ShmTest, HandleGet) { TEST_F(ShmTest, HandleGet) {
std::string name = generate_unique_name("handle_get"); std::string name = generate_unique_name("handle_get");
handle h(name.c_str(), 512); handle h(name.c_str(), 512);
void* mem = h.get(); void* mem = h.get();
EXPECT_NE(mem, nullptr); EXPECT_NE(mem, nullptr);
// Write and read test // Write and read test
const char* test_str = "Handle get test"; const char* test_str = "Handle get test";
std::strcpy(static_cast<char*>(mem), test_str); std::strcpy(static_cast<char*>(mem), test_str);
EXPECT_STREQ(static_cast<char*>(mem), test_str); EXPECT_STREQ(static_cast<char*>(mem), test_str);
} }
// Test handle detach() and attach() methods // Test handle detach() and attach() methods
TEST_F(ShmTest, HandleDetachAttach) { TEST_F(ShmTest, HandleDetachAttach) {
std::string name = generate_unique_name("handle_detach_attach"); std::string name = generate_unique_name("handle_detach_attach");
handle h1(name.c_str(), 256); handle h1(name.c_str(), 256);
ASSERT_TRUE(h1.valid()); ASSERT_TRUE(h1.valid());
id_t id = h1.detach(); id_t id = h1.detach();
EXPECT_NE(id, nullptr); EXPECT_NE(id, nullptr);
EXPECT_FALSE(h1.valid()); // Should be invalid after detach EXPECT_FALSE(h1.valid()); // Should be invalid after detach
handle h2; handle h2;
h2.attach(id); h2.attach(id);
EXPECT_TRUE(h2.valid()); EXPECT_TRUE(h2.valid());
// Clean up // Clean up
h2.release(); h2.release();
remove(id); remove(id);
} }
// Test writing and reading data through shared memory // Test writing and reading data through shared memory
TEST_F(ShmTest, WriteReadData) { TEST_F(ShmTest, WriteReadData) {
std::string name = generate_unique_name("write_read"); std::string name = generate_unique_name("write_read");
const std::size_t size = 1024; const std::size_t size = 1024;
handle h1(name.c_str(), size); handle h1(name.c_str(), size);
ASSERT_TRUE(h1.valid()); ASSERT_TRUE(h1.valid());
// Write test data // Write test data
struct TestData { struct TestData {
int value; int value;
char text[64]; char text[64];
}; };
TestData* data1 = static_cast<TestData*>(h1.get()); TestData* data1 = static_cast<TestData*>(h1.get());
data1->value = 42; data1->value = 42;
std::strcpy(data1->text, "Shared memory data"); std::strcpy(data1->text, "Shared memory data");
// Open in another "handle" (simulating different process) // Open in another "handle" (simulating different process)
handle h2(name.c_str(), size, open); handle h2(name.c_str(), size, open);
if (h2.valid()) { if (h2.valid()) {
TestData* data2 = static_cast<TestData*>(h2.get()); TestData* data2 = static_cast<TestData*>(h2.get());
EXPECT_EQ(data2->value, 42); EXPECT_EQ(data2->value, 42);
EXPECT_STREQ(data2->text, "Shared memory data"); EXPECT_STREQ(data2->text, "Shared memory data");
} }
} }
// Test handle with different modes // Test handle with different modes
TEST_F(ShmTest, HandleModes) { TEST_F(ShmTest, HandleModes) {
std::string name = generate_unique_name("handle_modes"); std::string name = generate_unique_name("handle_modes");
// Create only // Create only
handle h1(name.c_str(), 256, create); handle h1(name.c_str(), 256, create);
EXPECT_TRUE(h1.valid()); EXPECT_TRUE(h1.valid());
// Open existing // Open existing
handle h2(name.c_str(), 256, open); handle h2(name.c_str(), 256, open);
EXPECT_TRUE(h2.valid()); EXPECT_TRUE(h2.valid());
// Both modes // Both modes
handle h3(name.c_str(), 256, create | open); handle h3(name.c_str(), 256, create | open);
EXPECT_TRUE(h3.valid()); EXPECT_TRUE(h3.valid());
} }
// Test multiple handles to same shared memory // Test multiple handles to same shared memory
TEST_F(ShmTest, MultipleHandles) { TEST_F(ShmTest, MultipleHandles) {
std::string name = generate_unique_name("multiple_handles"); std::string name = generate_unique_name("multiple_handles");
const std::size_t size = 512; const std::size_t size = 512;
handle h1(name.c_str(), size); handle h1(name.c_str(), size);
handle h2(name.c_str(), size, open); handle h2(name.c_str(), size, open);
ASSERT_TRUE(h1.valid()); ASSERT_TRUE(h1.valid());
ASSERT_TRUE(h2.valid()); ASSERT_TRUE(h2.valid());
// Should point to same memory // Should point to same memory
int* data1 = static_cast<int*>(h1.get()); int* data1 = static_cast<int*>(h1.get());
int* data2 = static_cast<int*>(h2.get()); int* data2 = static_cast<int*>(h2.get());
*data1 = 12345; *data1 = 12345;
EXPECT_EQ(*data2, 12345); EXPECT_EQ(*data2, 12345);
} }
// Test large shared memory segment // Test large shared memory segment
TEST_F(ShmTest, LargeSegment) { TEST_F(ShmTest, LargeSegment) {
std::string name = generate_unique_name("large_segment"); std::string name = generate_unique_name("large_segment");
const std::size_t size = 10 * 1024 * 1024; // 10 MB const std::size_t size = 10 * 1024 * 1024; // 10 MB
handle h(name.c_str(), size); handle h(name.c_str(), size);
if (h.valid()) { if (h.valid()) {
EXPECT_GE(h.size(), size); EXPECT_GE(h.size(), size);
// Write pattern to a portion of memory // Write pattern to a portion of memory
char* mem = static_cast<char*>(h.get()); char* mem = static_cast<char*>(h.get());
for (std::size_t i = 0; i < 1024; ++i) { for (std::size_t i = 0; i < 1024; ++i) {
mem[i] = static_cast<char>(i % 256); mem[i] = static_cast<char>(i % 256);
} }
// Verify pattern // Verify pattern
for (std::size_t i = 0; i < 1024; ++i) { for (std::size_t i = 0; i < 1024; ++i) {
EXPECT_EQ(mem[i], static_cast<char>(i % 256)); EXPECT_EQ(mem[i], static_cast<char>(i % 256));
} }
} }
} }