mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 08:46:45 +08:00
test(mutex): add comprehensive unit tests for ipc::sync::mutex
- Test mutex construction (default and named) - Test lock/unlock operations - Test try_lock functionality - Test timed lock with various timeout values - Test critical section protection - Test concurrent access and contention scenarios - Test inter-thread synchronization with named mutex - Test resource cleanup (clear, clear_storage) - Test native handle access - Test edge cases (reopen, zero timeout, rapid operations) - Test exception safety of try_lock
This commit is contained in:
parent
280a21c89a
commit
b4ad3c69c9
501
test/test_mutex.cpp
Normal file
501
test/test_mutex.cpp
Normal file
@ -0,0 +1,501 @@
|
||||
/**
|
||||
* @file test_mutex.cpp
|
||||
* @brief Comprehensive unit tests for ipc::sync::mutex class
|
||||
*
|
||||
* This test suite covers:
|
||||
* - Mutex construction (default and named)
|
||||
* - Lock/unlock operations
|
||||
* - Try-lock functionality
|
||||
* - Timed lock with timeout
|
||||
* - Named mutex for inter-process synchronization
|
||||
* - Resource cleanup (clear, clear_storage)
|
||||
* - Native handle access
|
||||
* - Concurrent access scenarios
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include "libipc/mutex.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
using namespace ipc;
|
||||
using namespace ipc::sync;
|
||||
|
||||
namespace {
|
||||
|
||||
// Generate unique mutex names for tests
|
||||
std::string generate_unique_mutex_name(const char* prefix) {
|
||||
static int counter = 0;
|
||||
return std::string(prefix) + "_mutex_" + std::to_string(++counter);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class MutexTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
// Allow time for cleanup
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
};
|
||||
|
||||
// Test default constructor
|
||||
TEST_F(MutexTest, DefaultConstructor) {
|
||||
mutex mtx;
|
||||
// Default constructed mutex may or may not be valid depending on implementation
|
||||
// Just ensure it doesn't crash
|
||||
}
|
||||
|
||||
// Test named constructor
|
||||
TEST_F(MutexTest, NamedConstructor) {
|
||||
std::string name = generate_unique_mutex_name("named_ctor");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
EXPECT_TRUE(mtx.valid());
|
||||
}
|
||||
|
||||
// Test native() const method
|
||||
TEST_F(MutexTest, NativeConst) {
|
||||
std::string name = generate_unique_mutex_name("native_const");
|
||||
|
||||
const mutex mtx(name.c_str());
|
||||
const void* native_handle = mtx.native();
|
||||
|
||||
EXPECT_NE(native_handle, nullptr);
|
||||
}
|
||||
|
||||
// Test native() non-const method
|
||||
TEST_F(MutexTest, NativeNonConst) {
|
||||
std::string name = generate_unique_mutex_name("native_nonconst");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
void* native_handle = mtx.native();
|
||||
|
||||
EXPECT_NE(native_handle, nullptr);
|
||||
}
|
||||
|
||||
// Test valid() method
|
||||
TEST_F(MutexTest, Valid) {
|
||||
mutex mtx1;
|
||||
// May or may not be valid without open
|
||||
|
||||
std::string name = generate_unique_mutex_name("valid");
|
||||
mutex mtx2(name.c_str());
|
||||
EXPECT_TRUE(mtx2.valid());
|
||||
}
|
||||
|
||||
// Test open() method
|
||||
TEST_F(MutexTest, Open) {
|
||||
std::string name = generate_unique_mutex_name("open");
|
||||
|
||||
mutex mtx;
|
||||
bool result = mtx.open(name.c_str());
|
||||
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_TRUE(mtx.valid());
|
||||
}
|
||||
|
||||
// Test close() method
|
||||
TEST_F(MutexTest, Close) {
|
||||
std::string name = generate_unique_mutex_name("close");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
mtx.close();
|
||||
EXPECT_FALSE(mtx.valid());
|
||||
}
|
||||
|
||||
// Test clear() method
|
||||
TEST_F(MutexTest, Clear) {
|
||||
std::string name = generate_unique_mutex_name("clear");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
mtx.clear();
|
||||
EXPECT_FALSE(mtx.valid());
|
||||
}
|
||||
|
||||
// Test clear_storage() static method
|
||||
TEST_F(MutexTest, ClearStorage) {
|
||||
std::string name = generate_unique_mutex_name("clear_storage");
|
||||
|
||||
{
|
||||
mutex mtx(name.c_str());
|
||||
EXPECT_TRUE(mtx.valid());
|
||||
}
|
||||
|
||||
mutex::clear_storage(name.c_str());
|
||||
|
||||
// Try to open after clear - should create new or fail gracefully
|
||||
mutex mtx2(name.c_str());
|
||||
}
|
||||
|
||||
// Test basic lock and unlock
|
||||
TEST_F(MutexTest, LockUnlock) {
|
||||
std::string name = generate_unique_mutex_name("lock_unlock");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
bool locked = mtx.lock();
|
||||
EXPECT_TRUE(locked);
|
||||
|
||||
bool unlocked = mtx.unlock();
|
||||
EXPECT_TRUE(unlocked);
|
||||
}
|
||||
|
||||
// Test try_lock
|
||||
TEST_F(MutexTest, TryLock) {
|
||||
std::string name = generate_unique_mutex_name("try_lock");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
bool locked = mtx.try_lock();
|
||||
EXPECT_TRUE(locked);
|
||||
|
||||
if (locked) {
|
||||
mtx.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Test timed lock with infinite timeout
|
||||
TEST_F(MutexTest, TimedLockInfinite) {
|
||||
std::string name = generate_unique_mutex_name("timed_lock_inf");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
bool locked = mtx.lock(invalid_value);
|
||||
EXPECT_TRUE(locked);
|
||||
|
||||
if (locked) {
|
||||
mtx.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Test timed lock with timeout
|
||||
TEST_F(MutexTest, TimedLockTimeout) {
|
||||
std::string name = generate_unique_mutex_name("timed_lock_timeout");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
// Lock with 100ms timeout
|
||||
bool locked = mtx.lock(100);
|
||||
EXPECT_TRUE(locked);
|
||||
|
||||
if (locked) {
|
||||
mtx.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Test mutex protects critical section
|
||||
TEST_F(MutexTest, CriticalSection) {
|
||||
std::string name = generate_unique_mutex_name("critical_section");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
int shared_counter = 0;
|
||||
const int iterations = 100;
|
||||
|
||||
auto increment_task = [&]() {
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
mtx.lock();
|
||||
++shared_counter;
|
||||
mtx.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(increment_task);
|
||||
std::thread t2(increment_task);
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
EXPECT_EQ(shared_counter, iterations * 2);
|
||||
}
|
||||
|
||||
// Test concurrent try_lock
|
||||
TEST_F(MutexTest, ConcurrentTryLock) {
|
||||
std::string name = generate_unique_mutex_name("concurrent_try");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
std::atomic<int> success_count{0};
|
||||
std::atomic<int> fail_count{0};
|
||||
|
||||
auto try_lock_task = [&]() {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (mtx.try_lock()) {
|
||||
++success_count;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
mtx.unlock();
|
||||
} else {
|
||||
++fail_count;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(try_lock_task);
|
||||
std::thread t2(try_lock_task);
|
||||
std::thread t3(try_lock_task);
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
EXPECT_GT(success_count.load(), 0);
|
||||
// Some try_locks should succeed
|
||||
}
|
||||
|
||||
// Test lock contention
|
||||
TEST_F(MutexTest, LockContention) {
|
||||
std::string name = generate_unique_mutex_name("contention");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
std::atomic<bool> thread1_in_cs{false};
|
||||
std::atomic<bool> thread2_in_cs{false};
|
||||
std::atomic<bool> violation{false};
|
||||
|
||||
auto contention_task = [&](std::atomic<bool>& my_flag,
|
||||
std::atomic<bool>& other_flag) {
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
mtx.lock();
|
||||
|
||||
my_flag.store(true);
|
||||
if (other_flag.load()) {
|
||||
violation.store(true);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
|
||||
my_flag.store(false);
|
||||
mtx.unlock();
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// Should never have both threads in critical section
|
||||
EXPECT_FALSE(violation.load());
|
||||
}
|
||||
|
||||
// Test multiple lock/unlock cycles
|
||||
TEST_F(MutexTest, MultipleCycles) {
|
||||
std::string name = generate_unique_mutex_name("cycles");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_TRUE(mtx.lock());
|
||||
ASSERT_TRUE(mtx.unlock());
|
||||
}
|
||||
}
|
||||
|
||||
// Test timed lock timeout scenario
|
||||
TEST_F(MutexTest, TimedLockTimeoutScenario) {
|
||||
std::string name = generate_unique_mutex_name("timeout_scenario");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
// Lock in main thread
|
||||
ASSERT_TRUE(mtx.lock());
|
||||
|
||||
std::atomic<bool> timeout_occurred{false};
|
||||
|
||||
std::thread t([&]() {
|
||||
// Try to lock with short timeout - should timeout
|
||||
bool locked = mtx.lock(50); // 50ms timeout
|
||||
if (!locked) {
|
||||
timeout_occurred.store(true);
|
||||
} else {
|
||||
mtx.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
mtx.unlock();
|
||||
|
||||
t.join();
|
||||
|
||||
// Timeout should have occurred since we held the lock
|
||||
EXPECT_TRUE(timeout_occurred.load());
|
||||
}
|
||||
|
||||
// Test reopen after close
|
||||
TEST_F(MutexTest, ReopenAfterClose) {
|
||||
std::string name = generate_unique_mutex_name("reopen");
|
||||
|
||||
mutex mtx;
|
||||
|
||||
ASSERT_TRUE(mtx.open(name.c_str()));
|
||||
EXPECT_TRUE(mtx.valid());
|
||||
|
||||
mtx.close();
|
||||
EXPECT_FALSE(mtx.valid());
|
||||
|
||||
ASSERT_TRUE(mtx.open(name.c_str()));
|
||||
EXPECT_TRUE(mtx.valid());
|
||||
}
|
||||
|
||||
// Test named mutex inter-thread synchronization
|
||||
TEST_F(MutexTest, NamedMutexInterThread) {
|
||||
std::string name = generate_unique_mutex_name("inter_thread");
|
||||
|
||||
int shared_data = 0;
|
||||
std::atomic<bool> t1_done{false};
|
||||
std::atomic<bool> t2_done{false};
|
||||
|
||||
std::thread t1([&]() {
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
mtx.lock();
|
||||
shared_data = 100;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
mtx.unlock();
|
||||
|
||||
t1_done.store(true);
|
||||
});
|
||||
|
||||
std::thread t2([&]() {
|
||||
// Wait a bit to ensure t1 starts first
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
mtx.lock();
|
||||
EXPECT_TRUE(t1_done.load() || shared_data == 100);
|
||||
shared_data = 200;
|
||||
mtx.unlock();
|
||||
|
||||
t2_done.store(true);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
EXPECT_EQ(shared_data, 200);
|
||||
}
|
||||
|
||||
// Test exception safety of try_lock
|
||||
TEST_F(MutexTest, TryLockExceptionSafety) {
|
||||
std::string name = generate_unique_mutex_name("try_lock_exception");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
bool exception_thrown = false;
|
||||
try {
|
||||
mtx.try_lock();
|
||||
} catch (const std::system_error&) {
|
||||
exception_thrown = true;
|
||||
} catch (...) {
|
||||
FAIL() << "Unexpected exception type";
|
||||
}
|
||||
|
||||
// try_lock may throw system_error
|
||||
// Just ensure we can handle it
|
||||
}
|
||||
|
||||
// Test concurrent open/close operations
|
||||
TEST_F(MutexTest, ConcurrentOpenClose) {
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic<int> success_count{0};
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
threads.emplace_back([&, i]() {
|
||||
std::string name = generate_unique_mutex_name("concurrent");
|
||||
name += std::to_string(i);
|
||||
|
||||
mutex mtx;
|
||||
if (mtx.open(name.c_str())) {
|
||||
++success_count;
|
||||
mtx.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(success_count.load(), 5);
|
||||
}
|
||||
|
||||
// Test mutex with zero timeout
|
||||
TEST_F(MutexTest, ZeroTimeout) {
|
||||
std::string name = generate_unique_mutex_name("zero_timeout");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
// Lock with zero timeout (should try once and return)
|
||||
bool locked = mtx.lock(0);
|
||||
|
||||
if (locked) {
|
||||
mtx.unlock();
|
||||
}
|
||||
// Result may vary, just ensure it doesn't hang
|
||||
}
|
||||
|
||||
// Test rapid lock/unlock sequence
|
||||
TEST_F(MutexTest, RapidLockUnlock) {
|
||||
std::string name = generate_unique_mutex_name("rapid");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
auto rapid_task = [&]() {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
mtx.lock();
|
||||
mtx.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(rapid_task);
|
||||
std::thread t2(rapid_task);
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// Should complete without deadlock or crash
|
||||
}
|
||||
|
||||
// Test lock after clear
|
||||
TEST_F(MutexTest, LockAfterClear) {
|
||||
std::string name = generate_unique_mutex_name("lock_after_clear");
|
||||
|
||||
mutex mtx(name.c_str());
|
||||
ASSERT_TRUE(mtx.valid());
|
||||
|
||||
mtx.lock();
|
||||
mtx.unlock();
|
||||
|
||||
mtx.clear();
|
||||
EXPECT_FALSE(mtx.valid());
|
||||
|
||||
// Attempting to lock after clear should fail gracefully
|
||||
bool locked = mtx.lock();
|
||||
EXPECT_FALSE(locked);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user