simplify tests
This commit is contained in:
@@ -18,909 +18,119 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pimutex.h"
|
||||
#include "piprotectedvariable.h"
|
||||
#include "pistring.h"
|
||||
#include "pithread.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int THREAD_COUNT = 10;
|
||||
constexpr int ITERATIONS_PER_THREAD = 1000;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//! \~\brief Basic functionality tests
|
||||
//! \~\english Tests for basic constructor, set, get, and operator= functionality
|
||||
//! \~\russian Тесты базовой функциональности конструктора, set, get и оператора=
|
||||
TEST(PIProtectedVariable_Basic, ConstructorCreatesValidObject) {
|
||||
PIProtectedVariable<int> pv;
|
||||
// Note: PIProtectedVariable does not value-initialize its internal var,
|
||||
// so we just verify that the object is constructible and we can set/get values
|
||||
pv.set(42);
|
||||
EXPECT_EQ(pv.get(), 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, SetCorrectlySetsValue) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
EXPECT_EQ(pv.get(), 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, SetWithDifferentTypes) {
|
||||
// Basic functionality tests
|
||||
TEST(PIProtectedVariable_Basic, BasicFunctionality) {
|
||||
// Test basic set/get with different types
|
||||
PIProtectedVariable<int> pvInt;
|
||||
PIProtectedVariable<double> pvDouble;
|
||||
PIProtectedVariable<string> pvString;
|
||||
PIProtectedVariable<PIString> pvString;
|
||||
|
||||
pvInt.set(123);
|
||||
pvDouble.set(3.14159);
|
||||
pvString.set("Hello, World!");
|
||||
pvString.set(PIString("Hello, World!"));
|
||||
|
||||
EXPECT_EQ(pvInt.get(), 123);
|
||||
EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
|
||||
EXPECT_EQ(pvString.get(), "Hello, World!");
|
||||
}
|
||||
EXPECT_EQ(pvString.get(), PIString("Hello, World!"));
|
||||
|
||||
TEST(PIProtectedVariable_Basic, GetReturnsCopy) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(100);
|
||||
// Test operator=
|
||||
pvInt = 999;
|
||||
EXPECT_EQ(pvInt.get(), 999);
|
||||
|
||||
int val1 = pv.get();
|
||||
int val2 = pv.get();
|
||||
|
||||
// Modifying copies should not affect the protected variable
|
||||
val1 = 200;
|
||||
val2 = 300;
|
||||
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
EXPECT_EQ(val1, 200);
|
||||
EXPECT_EQ(val2, 300);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, GetReturnsCopyForObjectTypes) {
|
||||
struct TestStruct {
|
||||
int x;
|
||||
int y;
|
||||
TestStruct(): x(0), y(0) {}
|
||||
TestStruct(int a, int b): x(a), y(b) {}
|
||||
};
|
||||
|
||||
PIProtectedVariable<TestStruct> pv;
|
||||
pv.set(TestStruct(10, 20));
|
||||
|
||||
TestStruct val = pv.get();
|
||||
val.x = 100;
|
||||
val.y = 200;
|
||||
|
||||
// Original should be unchanged
|
||||
TestStruct original = pv.get();
|
||||
EXPECT_EQ(original.x, 10);
|
||||
EXPECT_EQ(original.y, 20);
|
||||
|
||||
// Verify the copy was modified
|
||||
EXPECT_EQ(val.x, 100);
|
||||
EXPECT_EQ(val.y, 200);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, OperatorAssignmentWorks) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv = 999;
|
||||
EXPECT_EQ(pv.get(), 999);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, OperatorAssignmentReturnsReference) {
|
||||
PIProtectedVariable<int> pv;
|
||||
PIProtectedVariable<int> & ref = (pv = 42);
|
||||
EXPECT_EQ(&ref, &pv);
|
||||
EXPECT_EQ(pv.get(), 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, EmptyDefaultConstructedVariable) {
|
||||
// Note: PIProtectedVariable does not value-initialize its internal var
|
||||
// We can only test that it's constructible and works after setting values
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0); // Set to zero explicitly
|
||||
EXPECT_EQ(pv.get(), 0);
|
||||
|
||||
PIProtectedVariable<string> pvStr;
|
||||
pvStr.set(""); // Set to empty explicitly
|
||||
EXPECT_TRUE(pvStr.get().empty());
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Basic, SetWithMoveSemantics) {
|
||||
PIProtectedVariable<string> pv;
|
||||
string movable = "This is a movable string";
|
||||
pv.set(std::move(movable));
|
||||
EXPECT_EQ(pv.get(), "This is a movable string");
|
||||
}
|
||||
|
||||
//! \~\brief Thread safety tests
|
||||
//! \~\english Tests for concurrent access safety
|
||||
//! \~\russian Тесты потокобезопасности при конкурентном доступе
|
||||
TEST(PIProtectedVariable_ThreadSafety, ConcurrentSetOperations) {
|
||||
PIProtectedVariable<int> pv;
|
||||
atomic<bool> start(false);
|
||||
|
||||
vector<thread> threads;
|
||||
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||
threads.emplace_back([&pv, &start, i]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) {
|
||||
pv.set(i * ITERATIONS_PER_THREAD + j);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
for (auto & t: threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
// The final value should be one of the values set by the threads
|
||||
// We can't predict which one, but it should be valid
|
||||
int finalVal = pv.get();
|
||||
EXPECT_GE(finalVal, 0);
|
||||
EXPECT_LT(finalVal, THREAD_COUNT * ITERATIONS_PER_THREAD);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_ThreadSafety, ConcurrentGetOperations) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
atomic<int> readCount(0);
|
||||
atomic<int> wrongCount(0);
|
||||
atomic<bool> start(false);
|
||||
|
||||
vector<thread> threads;
|
||||
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||
threads.emplace_back([&pv, &readCount, &wrongCount, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) {
|
||||
int val = pv.get();
|
||||
readCount++;
|
||||
if (val != 42) {
|
||||
wrongCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
for (auto & t: threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(readCount, THREAD_COUNT * ITERATIONS_PER_THREAD);
|
||||
EXPECT_EQ(wrongCount, 0);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_ThreadSafety, MixedReadWriteOperations) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0); // Initialize with a valid value
|
||||
atomic<int> counter(0);
|
||||
atomic<int> readCount(0);
|
||||
atomic<int> invalidReads(0);
|
||||
atomic<bool> start(false);
|
||||
const int NUM_WRITER_ITERATIONS = 1000;
|
||||
|
||||
// Writer thread
|
||||
thread writer([&pv, &counter, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int i = 0; i < NUM_WRITER_ITERATIONS; ++i) {
|
||||
int val = counter.fetch_add(1) + 1;
|
||||
pv.set(val);
|
||||
}
|
||||
});
|
||||
|
||||
// Reader threads
|
||||
vector<thread> readers;
|
||||
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||
readers.emplace_back([&pv, &readCount, &invalidReads, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int j = 0; j < NUM_WRITER_ITERATIONS; ++j) {
|
||||
int val = pv.get();
|
||||
readCount++;
|
||||
// Verify value is within expected range (0 to NUM_WRITER_ITERATIONS)
|
||||
if (val < 0 || val > NUM_WRITER_ITERATIONS) {
|
||||
invalidReads++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
writer.join();
|
||||
for (auto & t: readers) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Verify that counter reached expected value
|
||||
EXPECT_EQ(counter, NUM_WRITER_ITERATIONS);
|
||||
|
||||
// Verify that all reads were valid
|
||||
EXPECT_EQ(readCount, THREAD_COUNT * NUM_WRITER_ITERATIONS);
|
||||
EXPECT_EQ(invalidReads, 0);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_ThreadSafety, HeavyConcurrentAccess) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0); // Initialize with a valid value
|
||||
atomic<int> writeCount(0);
|
||||
atomic<int> readCount(0);
|
||||
atomic<bool> done(false);
|
||||
atomic<bool> start(false);
|
||||
|
||||
// Multiple writers
|
||||
vector<thread> writers;
|
||||
for (int i = 0; i < THREAD_COUNT / 2; ++i) {
|
||||
writers.emplace_back([&pv, &writeCount, &done, &start, i]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
while (!done.load()) {
|
||||
pv.set(i * 1000 + writeCount.fetch_add(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Multiple readers
|
||||
vector<thread> readers;
|
||||
for (int i = 0; i < THREAD_COUNT / 2; ++i) {
|
||||
readers.emplace_back([&pv, &readCount, &done, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
while (!done.load()) {
|
||||
int val = pv.get();
|
||||
readCount++;
|
||||
// Verify value is non-negative (writers only write positive values)
|
||||
if (val < 0) {
|
||||
FAIL() << "Invalid value read: " << val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
|
||||
// Run for a short time
|
||||
this_thread::sleep_for(chrono::milliseconds(500));
|
||||
done.store(true);
|
||||
|
||||
for (auto & t: writers) {
|
||||
t.join();
|
||||
}
|
||||
for (auto & t: readers) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_GT(writeCount, 0);
|
||||
EXPECT_GT(readCount, 0);
|
||||
}
|
||||
|
||||
//! \~\brief Pointer wrapper tests
|
||||
//! \~\english Tests for getRef() and Pointer wrapper functionality
|
||||
//! \~\russian Тесты обертки getRef() и Pointer
|
||||
TEST(PIProtectedVariable_Pointer, GetRefReturnsValidPointer) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
EXPECT_NE(&(*ptr), nullptr);
|
||||
EXPECT_EQ(*ptr, 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, OperatorArrowWorks) {
|
||||
// Test getRef() with Pointer
|
||||
struct TestStruct {
|
||||
int x = 10;
|
||||
int y = 20;
|
||||
int getValue() const { return x + y; }
|
||||
};
|
||||
|
||||
PIProtectedVariable<TestStruct> pv;
|
||||
pv.set(TestStruct());
|
||||
PIProtectedVariable<TestStruct> pvStruct;
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
auto ptr = pvStruct.getRef();
|
||||
EXPECT_EQ(ptr->x, 10);
|
||||
EXPECT_EQ(ptr->y, 20);
|
||||
EXPECT_EQ(ptr->getValue(), 30);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, OperatorDerefWorks) {
|
||||
PIProtectedVariable<string> pv;
|
||||
pv.set("original");
|
||||
// Modify through pointer
|
||||
*ptr = TestStruct();
|
||||
ptr->x = 100;
|
||||
EXPECT_EQ(pvStruct.get().x, 100);
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
*ptr = "modified";
|
||||
|
||||
EXPECT_EQ(pv.get(), "modified");
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, OperatorDerefAllowsModification) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(100);
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
(*ptr) += 50;
|
||||
|
||||
EXPECT_EQ(pv.get(), 150);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, MutexLockedWhenPointerCreated) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
atomic<bool> gotRef(false);
|
||||
atomic<bool> start(false);
|
||||
|
||||
// Start blocker thread but don't let it run yet
|
||||
thread blocker([&pv, &gotRef, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
auto p = pv.getRef();
|
||||
gotRef.store(true);
|
||||
});
|
||||
|
||||
// Get the lock in main thread
|
||||
auto ptr = pv.getRef();
|
||||
|
||||
// Now let blocker try to acquire
|
||||
start.store(true);
|
||||
|
||||
// Give the thread time to try to acquire the lock
|
||||
this_thread::sleep_for(chrono::milliseconds(100));
|
||||
|
||||
// The blocker should NOT have acquired the lock yet
|
||||
EXPECT_FALSE(gotRef.load());
|
||||
|
||||
// Release the first pointer by letting it go out of scope
|
||||
ptr.~Pointer();
|
||||
|
||||
blocker.join();
|
||||
EXPECT_TRUE(gotRef.load());
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, MutexUnlockedWhenPointerDestroyed) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
{
|
||||
auto ptr = pv.getRef();
|
||||
// ptr holds the lock
|
||||
(*ptr) = 100;
|
||||
} // ptr destroyed here, lock released
|
||||
|
||||
// Now we should be able to get another reference immediately
|
||||
auto ptr2 = pv.getRef();
|
||||
EXPECT_EQ(*ptr2, 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, PointerModifiesProtectedValue) {
|
||||
PIProtectedVariable<string> pv;
|
||||
pv.set("Hello");
|
||||
|
||||
{
|
||||
auto ptr = pv.getRef();
|
||||
*ptr += " World";
|
||||
}
|
||||
|
||||
EXPECT_EQ(pv.get(), "Hello World");
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Pointer, PointerWithComplexTypes) {
|
||||
struct ComplexType {
|
||||
vector<int> data;
|
||||
map<string, int> mapping;
|
||||
|
||||
ComplexType() {
|
||||
data = {1, 2, 3, 4, 5};
|
||||
mapping["one"] = 1;
|
||||
mapping["two"] = 2;
|
||||
}
|
||||
};
|
||||
|
||||
PIProtectedVariable<ComplexType> pv;
|
||||
pv.set(ComplexType());
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
ptr->data.push_back(6);
|
||||
ptr->mapping["three"] = 3;
|
||||
|
||||
EXPECT_EQ(pv.get().data.size(), 6u);
|
||||
EXPECT_EQ(pv.get().mapping.size(), 3u);
|
||||
EXPECT_EQ(pv.get().mapping["three"], 3);
|
||||
}
|
||||
|
||||
//! \~\brief Move semantics tests
|
||||
//! \~\english Tests for move constructor and lock ownership transfer
|
||||
//! \~\russian Тесты конструктора перемещения и передачи владения блокировкой
|
||||
TEST(PIProtectedVariable_Move, MoveConstructorWorks) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
// Test move semantics for Pointer
|
||||
pvInt.set(42);
|
||||
auto ptr1 = pvInt.getRef();
|
||||
auto ptr2 = std::move(ptr1);
|
||||
|
||||
// ptr2 should have access
|
||||
EXPECT_EQ(*ptr2, 42);
|
||||
*ptr2 = 100;
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
EXPECT_EQ(pvInt.get(), 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
// We can't directly test unlock count without modifying the class,
|
||||
// but we can test the behavior: after move, only the target should unlock
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
|
||||
// ptr1 holds the lock
|
||||
bool canAcquire = false;
|
||||
thread tryAcquire([&pv, &canAcquire]() {
|
||||
auto p = pv.getRef();
|
||||
canAcquire = true;
|
||||
});
|
||||
|
||||
this_thread::sleep_for(chrono::milliseconds(50));
|
||||
EXPECT_FALSE(canAcquire); // Lock still held by ptr1
|
||||
|
||||
// Move ptr1 to ptr2
|
||||
auto ptr2 = std::move(ptr1);
|
||||
|
||||
// ptr2 should still hold the lock
|
||||
this_thread::sleep_for(chrono::milliseconds(50));
|
||||
EXPECT_FALSE(canAcquire); // Lock still held (now by ptr2)
|
||||
|
||||
// Destroy ptr2
|
||||
ptr2.~Pointer();
|
||||
|
||||
tryAcquire.join();
|
||||
EXPECT_TRUE(canAcquire); // Now lock is free
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, SourceDoesNotUnlockAfterMove) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
// If source unlocked after move, we would get a double-unlock error
|
||||
// or undefined behavior. This test verifies that moving works correctly.
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
(*ptr1) = 100;
|
||||
|
||||
// Move ptr1
|
||||
auto ptr2 = std::move(ptr1);
|
||||
|
||||
// ptr2 should work correctly
|
||||
(*ptr2) = 200;
|
||||
EXPECT_EQ(pv.get(), 200);
|
||||
|
||||
// When ptr1 is destroyed, it should NOT unlock (ownsLock should be false)
|
||||
// When ptr2 is destroyed, it SHOULD unlock
|
||||
// This should complete without any errors
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, TargetUnlocksInDestructor) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
bool lockReleased = false;
|
||||
|
||||
{
|
||||
auto ptr1 = pv.getRef();
|
||||
auto ptr2 = std::move(ptr1);
|
||||
// ptr2 holds the lock, ptr1 does not
|
||||
} // ptr2 destroyed here, should release lock
|
||||
|
||||
// If we get here without deadlock, the lock was properly released
|
||||
auto ptr3 = pv.getRef();
|
||||
lockReleased = true;
|
||||
|
||||
EXPECT_TRUE(lockReleased);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, UnlockCalledExactlyOnceAfterMove) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
// Test that after move:
|
||||
// 1. Source Pointer's destructor does NOT call unlock()
|
||||
// 2. Target Pointer's destructor DOES call unlock()
|
||||
// 3. Total unlock() calls = 1 (not 2!)
|
||||
|
||||
{
|
||||
auto ptr1 = pv.getRef();
|
||||
auto ptr2 = std::move(ptr1);
|
||||
// ptr1.ownsLock should be false
|
||||
// ptr2.ownsLock should be true
|
||||
|
||||
// Both go out of scope
|
||||
// Only ptr2 should unlock
|
||||
}
|
||||
|
||||
// If we get here without issues, exactly one unlock happened
|
||||
EXPECT_EQ(pv.get(), 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, MoveWithRVODisabled) {
|
||||
// This test should be compiled with -fno-elide-constructors
|
||||
// to ensure move constructor is actually called
|
||||
|
||||
PIProtectedVariable<int> pv(42);
|
||||
|
||||
auto ptr_src = pv.getRef();
|
||||
*ptr_src = 99;
|
||||
auto ptr = std::move(ptr_src);
|
||||
*ptr = 100;
|
||||
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Move, MoveConstructorWithComplexTypes) {
|
||||
struct ComplexType {
|
||||
vector<int> data;
|
||||
string name;
|
||||
|
||||
ComplexType(): data{1, 2, 3}, name("test") {}
|
||||
};
|
||||
|
||||
PIProtectedVariable<ComplexType> pv;
|
||||
pv.set(ComplexType());
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
ptr1->data.push_back(4);
|
||||
ptr1->name = "modified";
|
||||
|
||||
auto ptr2 = std::move(ptr1);
|
||||
ptr2->data.push_back(5);
|
||||
|
||||
EXPECT_EQ(pv.get().data.size(), 5u);
|
||||
EXPECT_EQ(pv.get().name, "modified");
|
||||
}
|
||||
|
||||
//! \~\brief Copy prevention tests
|
||||
//! \~\english Tests that verify copy operations are deleted
|
||||
//! \~\russian Тесты проверки что операции копирования удалены
|
||||
TEST(PIProtectedVariable_CopyPrevention, CopyConstructorDeleted) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
auto ptr = pv.getRef();
|
||||
|
||||
// This should not compile:
|
||||
// auto ptr2 = ptr; // Copy constructor is deleted
|
||||
|
||||
// We verify this at compile time by ensuring the code compiles
|
||||
// without attempting to copy
|
||||
EXPECT_EQ(*ptr, 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_CopyPrevention, CopyAssignmentDeleted) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
auto ptr2 = pv.getRef(); // This will block until ptr1 is released
|
||||
|
||||
// This should not compile:
|
||||
// ptr1 = ptr2; // Copy assignment is deleted
|
||||
|
||||
(*ptr2) = 100;
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_CopyPrevention, MoveAssignmentDeleted) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
auto ptr1 = pv.getRef();
|
||||
auto ptr2 = pv.getRef(); // This will block until ptr1 is released
|
||||
|
||||
// This should not compile:
|
||||
// ptr1 = std::move(ptr2); // Move assignment is deleted
|
||||
|
||||
(*ptr2) = 100;
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_CopyPrevention, CompileTimeCheck) {
|
||||
// Compile-time verification that copy operations are deleted
|
||||
// These static_assert statements will fail to compile if copy operations exist
|
||||
|
||||
// Thread safety tests
|
||||
TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) {
|
||||
PIProtectedVariable<int> pv;
|
||||
|
||||
// The fact that this file compiles proves that:
|
||||
// 1. Pointer copy constructor is deleted
|
||||
// 2. Pointer copy assignment is deleted
|
||||
// 3. Pointer move assignment is deleted
|
||||
atomic<int> writeCount(0);
|
||||
atomic<int> readCount(0);
|
||||
atomic<int> invalidReads(0);
|
||||
const int NUM_ITERATIONS = 1000;
|
||||
const int NUM_WRITERS = 10;
|
||||
const int NUM_READERS = 20;
|
||||
const int TOTAL_WRITES = NUM_WRITERS * NUM_ITERATIONS;
|
||||
|
||||
EXPECT_TRUE(true); // Placeholder assertion
|
||||
}
|
||||
// Collect thread handles for joining
|
||||
PIVector<PIThread *> threads;
|
||||
|
||||
//! \~\brief Edge case tests
|
||||
//! \~\english Tests for edge cases and exception safety
|
||||
//! \~\russian Тесты граничных случаев и исключительной безопасности
|
||||
TEST(PIProtectedVariable_EdgeCases, MultipleGetRefCallsSequential) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
auto ptr = pv.getRef();
|
||||
(*ptr)++;
|
||||
}
|
||||
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, NestedGetRefInSeparateScopes) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0);
|
||||
|
||||
{
|
||||
auto ptr1 = pv.getRef();
|
||||
(*ptr1) += 10;
|
||||
}
|
||||
|
||||
{
|
||||
auto ptr2 = pv.getRef();
|
||||
(*ptr2) += 20;
|
||||
}
|
||||
|
||||
{
|
||||
auto ptr3 = pv.getRef();
|
||||
(*ptr3) += 30;
|
||||
}
|
||||
|
||||
EXPECT_EQ(pv.get(), 60);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, LargeNumberOfThreads) {
|
||||
PIProtectedVariable<int> pv;
|
||||
atomic<int> counter(0);
|
||||
atomic<bool> start(false);
|
||||
const int NUM_THREADS = 50;
|
||||
|
||||
vector<thread> threads;
|
||||
for (int i = 0; i < NUM_THREADS; ++i) {
|
||||
threads.emplace_back([&pv, &counter, &start]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
// Create writer threads
|
||||
for (int i = 0; i < NUM_WRITERS; ++i) {
|
||||
threads.push_back(new PIThread([&pv, &writeCount]() {
|
||||
for (int j = 0; j < NUM_ITERATIONS; ++j) {
|
||||
auto val = pv.getRef();
|
||||
(*val)++;
|
||||
writeCount++;
|
||||
}
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
int val = counter.fetch_add(1);
|
||||
pv.set(val);
|
||||
int readVal = pv.get();
|
||||
EXPECT_GE(readVal, 0);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
for (auto & t: threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(counter, NUM_THREADS * 100);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, ValueInitializationOfDifferentTypes) {
|
||||
// Note: PIProtectedVariable does not value-initialize its internal var
|
||||
// We test that setting and getting works for different types
|
||||
PIProtectedVariable<int> pvInt;
|
||||
PIProtectedVariable<double> pvDouble;
|
||||
PIProtectedVariable<bool> pvBool;
|
||||
PIProtectedVariable<string> pvString;
|
||||
PIProtectedVariable<void *> pvVoidPtr;
|
||||
|
||||
pvInt.set(0);
|
||||
pvDouble.set(0.0);
|
||||
pvBool.set(false);
|
||||
pvString.set("");
|
||||
pvVoidPtr.set(nullptr);
|
||||
|
||||
EXPECT_EQ(pvInt.get(), 0);
|
||||
EXPECT_DOUBLE_EQ(pvDouble.get(), 0.0);
|
||||
EXPECT_FALSE(pvBool.get());
|
||||
EXPECT_TRUE(pvString.get().empty());
|
||||
EXPECT_EQ(pvVoidPtr.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, NegativeAndExtremeValues) {
|
||||
PIProtectedVariable<int> pv;
|
||||
|
||||
pv.set(INT_MIN);
|
||||
EXPECT_EQ(pv.get(), INT_MIN);
|
||||
|
||||
pv.set(INT_MAX);
|
||||
EXPECT_EQ(pv.get(), INT_MAX);
|
||||
|
||||
pv.set(-1);
|
||||
EXPECT_EQ(pv.get(), -1);
|
||||
|
||||
pv.set(0);
|
||||
EXPECT_EQ(pv.get(), 0);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, EmptyStringAndContainer) {
|
||||
PIProtectedVariable<string> pvStr;
|
||||
PIProtectedVariable<vector<int>> pvVec;
|
||||
|
||||
pvStr.set("");
|
||||
EXPECT_TRUE(pvStr.get().empty());
|
||||
|
||||
pvVec.set(vector<int>());
|
||||
EXPECT_TRUE(pvVec.get().empty());
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_EdgeCases, PointerLifetime) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(42);
|
||||
|
||||
int * rawPtr = nullptr;
|
||||
{
|
||||
auto ptr = pv.getRef();
|
||||
rawPtr = &(*ptr);
|
||||
// ptr destroyed here
|
||||
}
|
||||
|
||||
// rawPtr still points to valid memory (pv.var)
|
||||
// but we shouldn't modify through it without holding the lock
|
||||
EXPECT_EQ(*rawPtr, 42);
|
||||
}
|
||||
|
||||
//! \~\brief Exception safety tests
|
||||
//! \~\english Tests for exception safety
|
||||
//! \~\russian Тесты исключительной безопасности
|
||||
TEST(PIProtectedVariable_ExceptionSafety, SetWithMoveDoesNotLeaveInBadState) {
|
||||
struct MovesOnly {
|
||||
int value;
|
||||
MovesOnly(): value(0) {}
|
||||
MovesOnly(int v): value(v) {}
|
||||
MovesOnly(const MovesOnly &) = delete; // Copy is deleted
|
||||
MovesOnly(MovesOnly &&) noexcept = default;
|
||||
MovesOnly & operator=(const MovesOnly &) = delete;
|
||||
MovesOnly & operator=(MovesOnly &&) noexcept = default;
|
||||
};
|
||||
|
||||
PIProtectedVariable<MovesOnly> pv;
|
||||
pv.set(MovesOnly(42));
|
||||
|
||||
// Use getRef to access the value since get() requires copy
|
||||
auto ptr = pv.getRef();
|
||||
EXPECT_EQ(ptr->value, 42);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_ExceptionSafety, GetRefDoesNotLeakLockOnException) {
|
||||
PIProtectedVariable<int> pv;
|
||||
pv.set(0);
|
||||
|
||||
bool exceptionThrown = false;
|
||||
try {
|
||||
auto ptr = pv.getRef();
|
||||
(*ptr) = 100;
|
||||
// ptr goes out of scope normally, lock released
|
||||
} catch (...) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
EXPECT_FALSE(exceptionThrown);
|
||||
EXPECT_EQ(pv.get(), 100);
|
||||
|
||||
// If lock was leaked, this would deadlock
|
||||
auto ptr2 = pv.getRef();
|
||||
EXPECT_EQ(*ptr2, 100);
|
||||
}
|
||||
|
||||
//! \~\brief Integration tests
|
||||
//! \~\english Integration tests combining multiple features
|
||||
//! \~\russian Интеграционные тесты, сочетающие несколько функций
|
||||
TEST(PIProtectedVariable_Integration, ProducerConsumerPattern) {
|
||||
PIProtectedVariable<int> buffer;
|
||||
atomic<bool> done(false);
|
||||
atomic<int> produced(0);
|
||||
atomic<int> consumed(0);
|
||||
atomic<bool> start(false);
|
||||
const int NUM_ITEMS = 1000;
|
||||
|
||||
// Producer
|
||||
thread producer([&]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int i = 0; i < NUM_ITEMS; ++i) {
|
||||
auto ptr = buffer.getRef();
|
||||
*ptr = i;
|
||||
produced++;
|
||||
}
|
||||
done.store(true);
|
||||
});
|
||||
|
||||
// Consumer - consumes exactly NUM_ITEMS
|
||||
thread consumer([&]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int i = 0; i < NUM_ITEMS; ++i) {
|
||||
// Wait for producer to produce something
|
||||
while (produced.load() <= consumed.load() && !done.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
auto ptr = buffer.getRef();
|
||||
int val = *ptr;
|
||||
consumed++;
|
||||
(void)val; // Suppress unused warning
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true);
|
||||
producer.join();
|
||||
consumer.join();
|
||||
|
||||
EXPECT_EQ(produced, NUM_ITEMS);
|
||||
EXPECT_EQ(consumed, NUM_ITEMS);
|
||||
}
|
||||
|
||||
TEST(PIProtectedVariable_Integration, ReadMostlyPattern) {
|
||||
PIProtectedVariable<int> config;
|
||||
config.set(100);
|
||||
|
||||
atomic<int> totalReads(0);
|
||||
atomic<int> wrongReads(0);
|
||||
atomic<bool> start(false);
|
||||
atomic<bool> done(false);
|
||||
|
||||
// Readers
|
||||
vector<thread> readers;
|
||||
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||
readers.emplace_back([&]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
while (!done.load()) {
|
||||
int val = config.get();
|
||||
totalReads++;
|
||||
if (val < 100 || val > 200) {
|
||||
wrongReads++;
|
||||
// Create reader threads
|
||||
for (int i = 0; i < NUM_READERS; ++i) {
|
||||
threads.push_back(new PIThread([&pv, &invalidReads, &readCount]() {
|
||||
for (int j = 0; j < NUM_ITERATIONS; ++j) {
|
||||
auto val = pv.get();
|
||||
readCount++;
|
||||
// Value should always be in valid range [0, TOTAL_WRITES]
|
||||
if (val < 0 || val > TOTAL_WRITES) {
|
||||
invalidReads++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// Writer
|
||||
thread writer([&]() {
|
||||
while (!start.load()) {
|
||||
this_thread::yield();
|
||||
}
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
config.set(100 + i);
|
||||
this_thread::sleep_for(chrono::milliseconds(10));
|
||||
}
|
||||
done.store(true);
|
||||
});
|
||||
|
||||
start.store(true);
|
||||
writer.join();
|
||||
for (auto & t: readers) {
|
||||
t.join();
|
||||
// Start all threads
|
||||
for (auto * t: threads) {
|
||||
t->startOnce();
|
||||
}
|
||||
|
||||
EXPECT_GT(totalReads, 0);
|
||||
EXPECT_EQ(wrongReads, 0);
|
||||
// Wait for all threads to finish
|
||||
for (auto * t: threads) {
|
||||
t->waitForFinish(PISystemTime::fromSeconds(5));
|
||||
delete t;
|
||||
}
|
||||
|
||||
// Verify results
|
||||
EXPECT_EQ(writeCount, TOTAL_WRITES);
|
||||
EXPECT_EQ(readCount, NUM_READERS * NUM_ITERATIONS);
|
||||
EXPECT_EQ(invalidReads, 0) << "All reads should return valid values in range [0, " << TOTAL_WRITES << "]";
|
||||
|
||||
// Final value should be TOTAL_WRITES
|
||||
int finalVal = pv.get();
|
||||
EXPECT_EQ(finalVal, TOTAL_WRITES);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user