mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 08:46:45 +08:00
1. ChannelTest::MultipleSendersReceivers - Add C++14-compatible latch implementation (similar to C++20 std::latch) - Ensure receivers are ready before senders start sending messages - This prevents race condition where senders might send before receivers are listening 2. RWLockTest::ReadWriteReadPattern - Fix test logic: lock_shared allows multiple concurrent readers - Previous test had race condition where both threads could read same value - New test: each thread writes based on thread id (1 or 2), then reads - Expected result: 1*20 + 2*20 = 60 3. ShmTest::ReleaseMemory - Correct return value semantics: release() returns ref count before decrement, or -1 on error - Must call get_mem() to map memory and set ref count to 1 before release - Expected: release() returns 1 (ref count before decrement) 4. ShmTest::ReferenceCount - Correct semantics: ref count is 0 after acquire (memory not mapped) - get_mem() maps memory and sets ref count to 1 - Second acquire+get_mem increases ref count to 2 - Test now properly validates reference counting behavior 5. ShmTest::SubtractReference - sub_ref() only works after get_mem() has mapped the memory - Must call get_mem() first to initialize ref count to 1 - sub_ref() then decrements it to 0 - Test now follows correct API usage pattern
522 lines
14 KiB
C++
522 lines
14 KiB
C++
/**
|
|
* @file test_shm.cpp
|
|
* @brief Comprehensive unit tests for ipc::shm (shared memory) functionality
|
|
*
|
|
* This test suite covers:
|
|
* - Low-level shared memory functions (acquire, get_mem, release, remove)
|
|
* - Reference counting (get_ref, sub_ref)
|
|
* - High-level handle class interface
|
|
* - Create and open modes
|
|
* - Resource cleanup and error handling
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string>
|
|
#include "libipc/shm.h"
|
|
|
|
using namespace ipc;
|
|
|
|
namespace {
|
|
|
|
// Generate unique shared memory names for tests
|
|
std::string generate_unique_name(const char* prefix) {
|
|
static int counter = 0;
|
|
return std::string(prefix) + "_test_" + std::to_string(++counter);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
class ShmTest : public ::testing::Test {
|
|
protected:
|
|
void TearDown() override {
|
|
// Clean up any leftover shared memory segments
|
|
}
|
|
};
|
|
|
|
// ========== Low-level API Tests ==========
|
|
|
|
// Test acquire with create mode
|
|
TEST_F(ShmTest, AcquireCreate) {
|
|
std::string name = generate_unique_name("acquire_create");
|
|
const std::size_t size = 1024;
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
std::size_t actual_size = 0;
|
|
void* mem = shm::get_mem(id, &actual_size);
|
|
EXPECT_NE(mem, nullptr);
|
|
EXPECT_GE(actual_size, size);
|
|
|
|
// Use remove(id) to clean up - it internally calls release()
|
|
shm::remove(id);
|
|
}
|
|
|
|
// Test acquire with open mode (should fail if not exists)
|
|
TEST_F(ShmTest, AcquireOpenNonExistent) {
|
|
std::string name = generate_unique_name("acquire_open_fail");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 1024, shm::open);
|
|
// Opening non-existent shared memory should return nullptr or handle failure gracefully
|
|
if (id != nullptr) {
|
|
shm::release(id);
|
|
}
|
|
}
|
|
|
|
// Test acquire with both create and open modes
|
|
TEST_F(ShmTest, AcquireCreateOrOpen) {
|
|
std::string name = generate_unique_name("acquire_both");
|
|
const std::size_t size = 2048;
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create | shm::open);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
std::size_t actual_size = 0;
|
|
void* mem = shm::get_mem(id, &actual_size);
|
|
EXPECT_NE(mem, nullptr);
|
|
EXPECT_GE(actual_size, size);
|
|
|
|
// Use remove(id) to clean up - it internally calls release()
|
|
shm::remove(id);
|
|
}
|
|
|
|
// Test get_mem function
|
|
TEST_F(ShmTest, GetMemory) {
|
|
std::string name = generate_unique_name("get_mem");
|
|
const std::size_t size = 512;
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
std::size_t returned_size = 0;
|
|
void* mem = shm::get_mem(id, &returned_size);
|
|
|
|
EXPECT_NE(mem, nullptr);
|
|
EXPECT_GE(returned_size, size);
|
|
|
|
// Write and read test data
|
|
const char* test_data = "Shared memory test data";
|
|
std::strcpy(static_cast<char*>(mem), test_data);
|
|
EXPECT_STREQ(static_cast<char*>(mem), test_data);
|
|
|
|
// Use remove(id) to clean up - it internally calls release()
|
|
shm::remove(id);
|
|
}
|
|
|
|
// Test get_mem without size parameter
|
|
TEST_F(ShmTest, GetMemoryNoSize) {
|
|
std::string name = generate_unique_name("get_mem_no_size");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
void* mem = shm::get_mem(id, nullptr);
|
|
EXPECT_NE(mem, nullptr);
|
|
|
|
// Use remove(id) to clean up - it internally calls release()
|
|
shm::remove(id);
|
|
}
|
|
|
|
// Test release function
|
|
TEST_F(ShmTest, ReleaseMemory) {
|
|
std::string name = generate_unique_name("release");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 128, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
// Must call get_mem to map memory and set reference count
|
|
void* mem = shm::get_mem(id, nullptr);
|
|
ASSERT_NE(mem, nullptr);
|
|
|
|
// release returns the reference count before decrement, or -1 on error
|
|
std::int32_t ref_count = shm::release(id);
|
|
EXPECT_EQ(ref_count, 1); // Should be 1 (set by get_mem, before decrement)
|
|
|
|
shm::remove(name.c_str());
|
|
}
|
|
|
|
// Test remove by id
|
|
TEST_F(ShmTest, RemoveById) {
|
|
std::string name = generate_unique_name("remove_by_id");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
// remove(id) internally calls release(id), so we don't need to call release first
|
|
shm::remove(id); // Should succeed
|
|
}
|
|
|
|
// Test remove by name
|
|
TEST_F(ShmTest, RemoveByName) {
|
|
std::string name = generate_unique_name("remove_by_name");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
shm::release(id);
|
|
shm::remove(name.c_str()); // Should succeed
|
|
}
|
|
|
|
// Test reference counting
|
|
TEST_F(ShmTest, ReferenceCount) {
|
|
std::string name = generate_unique_name("ref_count");
|
|
|
|
shm::id_t id1 = shm::acquire(name.c_str(), 512, shm::create);
|
|
ASSERT_NE(id1, nullptr);
|
|
|
|
// Reference count is 0 after acquire (memory not mapped yet)
|
|
std::int32_t ref_before_get_mem = shm::get_ref(id1);
|
|
EXPECT_EQ(ref_before_get_mem, 0);
|
|
|
|
// get_mem maps memory and sets reference count to 1
|
|
void* mem1 = shm::get_mem(id1, nullptr);
|
|
ASSERT_NE(mem1, nullptr);
|
|
|
|
std::int32_t ref1 = shm::get_ref(id1);
|
|
EXPECT_EQ(ref1, 1);
|
|
|
|
// Acquire again and get_mem (should increase reference count)
|
|
shm::id_t id2 = shm::acquire(name.c_str(), 512, shm::open);
|
|
if (id2 != nullptr) {
|
|
void* mem2 = shm::get_mem(id2, nullptr);
|
|
ASSERT_NE(mem2, nullptr);
|
|
|
|
std::int32_t ref2 = shm::get_ref(id2);
|
|
EXPECT_EQ(ref2, 2); // Should be 2 now
|
|
|
|
shm::release(id2);
|
|
}
|
|
|
|
shm::release(id1);
|
|
shm::remove(name.c_str());
|
|
}
|
|
|
|
// Test sub_ref function
|
|
TEST_F(ShmTest, SubtractReference) {
|
|
std::string name = generate_unique_name("sub_ref");
|
|
|
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
|
ASSERT_NE(id, nullptr);
|
|
|
|
// Must call get_mem first to map memory and initialize reference count
|
|
void* mem = shm::get_mem(id, nullptr);
|
|
ASSERT_NE(mem, nullptr);
|
|
|
|
std::int32_t ref_before = shm::get_ref(id);
|
|
EXPECT_EQ(ref_before, 1); // Should be 1 after get_mem
|
|
|
|
shm::sub_ref(id);
|
|
|
|
std::int32_t ref_after = shm::get_ref(id);
|
|
EXPECT_EQ(ref_after, 0); // Should be 0 after sub_ref
|
|
|
|
// Use remove(id) to clean up - it internally calls release()
|
|
shm::remove(id);
|
|
}
|
|
|
|
// ========== High-level handle class Tests ==========
|
|
|
|
// Test default handle constructor
|
|
TEST_F(ShmTest, HandleDefaultConstructor) {
|
|
shm::handle h;
|
|
EXPECT_FALSE(h.valid());
|
|
EXPECT_EQ(h.size(), 0u);
|
|
EXPECT_EQ(h.get(), nullptr);
|
|
}
|
|
|
|
// Test handle constructor with name and size
|
|
TEST_F(ShmTest, HandleConstructorWithParams) {
|
|
std::string name = generate_unique_name("handle_ctor");
|
|
const std::size_t size = 1024;
|
|
|
|
shm::handle h(name.c_str(), size);
|
|
|
|
EXPECT_TRUE(h.valid());
|
|
EXPECT_GE(h.size(), size);
|
|
EXPECT_NE(h.get(), nullptr);
|
|
EXPECT_STREQ(h.name(), name.c_str());
|
|
}
|
|
|
|
// Test handle move constructor
|
|
TEST_F(ShmTest, HandleMoveConstructor) {
|
|
std::string name = generate_unique_name("handle_move");
|
|
|
|
shm::handle h1(name.c_str(), 512);
|
|
ASSERT_TRUE(h1.valid());
|
|
|
|
void* ptr1 = h1.get();
|
|
std::size_t size1 = h1.size();
|
|
|
|
shm::handle h2(std::move(h1));
|
|
|
|
EXPECT_TRUE(h2.valid());
|
|
EXPECT_EQ(h2.get(), ptr1);
|
|
EXPECT_EQ(h2.size(), size1);
|
|
|
|
// h1 should be invalid after move
|
|
EXPECT_FALSE(h1.valid());
|
|
}
|
|
|
|
// Test handle swap
|
|
TEST_F(ShmTest, HandleSwap) {
|
|
std::string name1 = generate_unique_name("handle_swap1");
|
|
std::string name2 = generate_unique_name("handle_swap2");
|
|
|
|
shm::handle h1(name1.c_str(), 256);
|
|
shm::handle h2(name2.c_str(), 512);
|
|
|
|
void* ptr1 = h1.get();
|
|
void* ptr2 = h2.get();
|
|
std::size_t size1 = h1.size();
|
|
std::size_t size2 = h2.size();
|
|
|
|
h1.swap(h2);
|
|
|
|
EXPECT_EQ(h1.get(), ptr2);
|
|
EXPECT_EQ(h1.size(), size2);
|
|
EXPECT_EQ(h2.get(), ptr1);
|
|
EXPECT_EQ(h2.size(), size1);
|
|
}
|
|
|
|
// Test handle assignment operator
|
|
TEST_F(ShmTest, HandleAssignment) {
|
|
std::string name = generate_unique_name("handle_assign");
|
|
|
|
shm::handle h1(name.c_str(), 768);
|
|
void* ptr1 = h1.get();
|
|
|
|
shm::handle h2;
|
|
h2 = std::move(h1);
|
|
|
|
EXPECT_TRUE(h2.valid());
|
|
EXPECT_EQ(h2.get(), ptr1);
|
|
EXPECT_FALSE(h1.valid());
|
|
}
|
|
|
|
// Test handle valid() method
|
|
TEST_F(ShmTest, HandleValid) {
|
|
shm::handle h1;
|
|
EXPECT_FALSE(h1.valid());
|
|
|
|
std::string name = generate_unique_name("handle_valid");
|
|
shm::handle h2(name.c_str(), 128);
|
|
EXPECT_TRUE(h2.valid());
|
|
}
|
|
|
|
// Test handle size() method
|
|
TEST_F(ShmTest, HandleSize) {
|
|
std::string name = generate_unique_name("handle_size");
|
|
const std::size_t requested_size = 2048;
|
|
|
|
shm::handle h(name.c_str(), requested_size);
|
|
|
|
EXPECT_GE(h.size(), requested_size);
|
|
}
|
|
|
|
// Test handle name() method
|
|
TEST_F(ShmTest, HandleName) {
|
|
std::string name = generate_unique_name("handle_name");
|
|
|
|
shm::handle h(name.c_str(), 256);
|
|
|
|
EXPECT_STREQ(h.name(), name.c_str());
|
|
}
|
|
|
|
// Test handle ref() method
|
|
TEST_F(ShmTest, HandleRef) {
|
|
std::string name = generate_unique_name("handle_ref");
|
|
|
|
shm::handle h(name.c_str(), 256);
|
|
|
|
std::int32_t ref = h.ref();
|
|
EXPECT_GT(ref, 0);
|
|
}
|
|
|
|
// Test handle sub_ref() method
|
|
TEST_F(ShmTest, HandleSubRef) {
|
|
std::string name = generate_unique_name("handle_sub_ref");
|
|
|
|
shm::handle h(name.c_str(), 256);
|
|
|
|
std::int32_t ref_before = h.ref();
|
|
h.sub_ref();
|
|
std::int32_t ref_after = h.ref();
|
|
|
|
EXPECT_EQ(ref_after, ref_before - 1);
|
|
}
|
|
|
|
// Test handle acquire() method
|
|
TEST_F(ShmTest, HandleAcquire) {
|
|
shm::handle h;
|
|
EXPECT_FALSE(h.valid());
|
|
|
|
std::string name = generate_unique_name("handle_acquire");
|
|
bool result = h.acquire(name.c_str(), 512);
|
|
|
|
EXPECT_TRUE(result);
|
|
EXPECT_TRUE(h.valid());
|
|
EXPECT_GE(h.size(), 512u);
|
|
}
|
|
|
|
// Test handle release() method
|
|
TEST_F(ShmTest, HandleRelease) {
|
|
std::string name = generate_unique_name("handle_release");
|
|
|
|
shm::handle h(name.c_str(), 256);
|
|
ASSERT_TRUE(h.valid());
|
|
|
|
std::int32_t ref_count = h.release();
|
|
EXPECT_GE(ref_count, 0);
|
|
}
|
|
|
|
// Test handle clear() method
|
|
TEST_F(ShmTest, HandleClear) {
|
|
std::string name = generate_unique_name("handle_clear");
|
|
|
|
shm::handle h(name.c_str(), 256);
|
|
ASSERT_TRUE(h.valid());
|
|
|
|
h.clear();
|
|
EXPECT_FALSE(h.valid());
|
|
}
|
|
|
|
// Test handle clear_storage() static method
|
|
TEST_F(ShmTest, HandleClearStorage) {
|
|
std::string name = generate_unique_name("handle_clear_storage");
|
|
|
|
{
|
|
shm::handle h(name.c_str(), 256);
|
|
EXPECT_TRUE(h.valid());
|
|
}
|
|
|
|
shm::handle::clear_storage(name.c_str());
|
|
|
|
// Try to open - should fail or create new
|
|
shm::handle h2(name.c_str(), 256, shm::open);
|
|
// Behavior depends on implementation
|
|
}
|
|
|
|
// Test handle get() method
|
|
TEST_F(ShmTest, HandleGet) {
|
|
std::string name = generate_unique_name("handle_get");
|
|
|
|
shm::handle h(name.c_str(), 512);
|
|
|
|
void* mem = h.get();
|
|
EXPECT_NE(mem, nullptr);
|
|
|
|
// Write and read test
|
|
const char* test_str = "Handle get test";
|
|
std::strcpy(static_cast<char*>(mem), test_str);
|
|
EXPECT_STREQ(static_cast<char*>(mem), test_str);
|
|
}
|
|
|
|
// Test handle detach() and attach() methods
|
|
TEST_F(ShmTest, HandleDetachAttach) {
|
|
std::string name = generate_unique_name("handle_detach_attach");
|
|
|
|
shm::handle h1(name.c_str(), 256);
|
|
ASSERT_TRUE(h1.valid());
|
|
|
|
shm::id_t id = h1.detach();
|
|
EXPECT_NE(id, nullptr);
|
|
EXPECT_FALSE(h1.valid()); // Should be invalid after detach
|
|
|
|
shm::handle h2;
|
|
h2.attach(id);
|
|
EXPECT_TRUE(h2.valid());
|
|
|
|
// Clean up - use h2.clear() or shm::remove(id) alone, not both
|
|
// Option 1: Use handle's clear() which calls shm::remove(id) internally
|
|
id = h2.detach(); // Detach first to get the id without releasing
|
|
shm::remove(id); // Then remove to clean up both memory and disk file
|
|
}
|
|
|
|
// Test writing and reading data through shared memory
|
|
TEST_F(ShmTest, WriteReadData) {
|
|
std::string name = generate_unique_name("write_read");
|
|
const std::size_t size = 1024;
|
|
|
|
shm::handle h1(name.c_str(), size);
|
|
ASSERT_TRUE(h1.valid());
|
|
|
|
// Write test data
|
|
struct TestData {
|
|
int value;
|
|
char text[64];
|
|
};
|
|
|
|
TestData* data1 = static_cast<TestData*>(h1.get());
|
|
data1->value = 42;
|
|
std::strcpy(data1->text, "Shared memory data");
|
|
|
|
// Open in another "shm::handle" (simulating different process)
|
|
shm::handle h2(name.c_str(), size, shm::open);
|
|
if (h2.valid()) {
|
|
TestData* data2 = static_cast<TestData*>(h2.get());
|
|
EXPECT_EQ(data2->value, 42);
|
|
EXPECT_STREQ(data2->text, "Shared memory data");
|
|
}
|
|
}
|
|
|
|
// Test handle with different modes
|
|
TEST_F(ShmTest, HandleModes) {
|
|
std::string name = generate_unique_name("handle_modes");
|
|
|
|
// Create only
|
|
shm::handle h1(name.c_str(), 256, shm::create);
|
|
EXPECT_TRUE(h1.valid());
|
|
|
|
// Open existing
|
|
shm::handle h2(name.c_str(), 256, shm::open);
|
|
EXPECT_TRUE(h2.valid());
|
|
|
|
// Both modes
|
|
shm::handle h3(name.c_str(), 256, shm::create | shm::open);
|
|
EXPECT_TRUE(h3.valid());
|
|
}
|
|
|
|
// Test multiple handles to same shared memory
|
|
TEST_F(ShmTest, MultipleHandles) {
|
|
std::string name = generate_unique_name("multiple_handles");
|
|
const std::size_t size = 512;
|
|
|
|
shm::handle h1(name.c_str(), size);
|
|
shm::handle h2(name.c_str(), size, shm::open);
|
|
|
|
ASSERT_TRUE(h1.valid());
|
|
ASSERT_TRUE(h2.valid());
|
|
|
|
// Should point to same memory
|
|
int* data1 = static_cast<int*>(h1.get());
|
|
int* data2 = static_cast<int*>(h2.get());
|
|
|
|
*data1 = 12345;
|
|
EXPECT_EQ(*data2, 12345);
|
|
}
|
|
|
|
// Test large shared memory segment
|
|
TEST_F(ShmTest, LargeSegment) {
|
|
std::string name = generate_unique_name("large_segment");
|
|
const std::size_t size = 10 * 1024 * 1024; // 10 MB
|
|
|
|
shm::handle h(name.c_str(), size);
|
|
|
|
if (h.valid()) {
|
|
EXPECT_GE(h.size(), size);
|
|
|
|
// Write pattern to a portion of memory
|
|
char* mem = static_cast<char*>(h.get());
|
|
for (std::size_t i = 0; i < 1024; ++i) {
|
|
mem[i] = static_cast<char>(i % 256);
|
|
}
|
|
|
|
// Verify pattern
|
|
for (std::size_t i = 0; i < 1024; ++i) {
|
|
EXPECT_EQ(mem[i], static_cast<char>(i % 256));
|
|
}
|
|
}
|
|
}
|