simplify piprotectedvariable

This commit is contained in:
2026-03-17 17:33:27 +03:00
parent 2798d7de9c
commit 8ccc05ee78
3 changed files with 964 additions and 21 deletions

View File

@@ -4,22 +4,22 @@
//! \~english Thread-safe variable
//! \~russian Потокобезопасная переменная
/*
PIP - Platform Independent Primitives
Thread-safe variable
Ivan Pelipenko peri4ko@yandex.ru
PIP - Platform Independent Primitives
Thread-safe variable
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PIPROTECTEDVARIABLE_H
@@ -39,16 +39,22 @@ public:
//! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным.
class PIP_EXPORT Pointer {
friend class PIProtectedVariable<T>;
NO_COPY_CLASS(Pointer);
public:
//! \~english Copies wrapper state for access to the same protected value.
//! \~russian Копирует состояние обертки для доступа к тому же защищенному значению.
Pointer(const Pointer & v): pv(v.pv), counter(v.counter + 1) {}
//! \~\english Move constructor - transfers ownership of the lock.
//! \~russian Конструктор перемещения - передает владение блокировкой.
Pointer(Pointer && other) noexcept: pv(other.pv), ownsLock(other.ownsLock) { other.ownsLock = false; }
//! \~english Destroys wrapper and releases the mutex when it owns the original lock.
//! \~russian Уничтожает обертку и освобождает мьютекс, когда она владеет исходной блокировкой.
//! \~\english Move assignment is deleted - Pointer can only be moved once.
//! \~russian Оператор перемещения удален - Pointer можно переместить только один раз.
Pointer & operator=(Pointer &&) = delete;
//! \~\english Destroys wrapper and releases the mutex.
//! \~russian Уничтожает обертку и освобождает мьютекс.
~Pointer() {
if (counter == 0) pv.mutex.unlock();
if (ownsLock) pv.mutex.unlock();
}
//! \~english Returns pointer access to the protected value.
@@ -61,10 +67,10 @@ public:
private:
Pointer() = delete;
Pointer(PIProtectedVariable<T> & v): pv(v) {}
Pointer(PIProtectedVariable<T> & v): pv(v), ownsLock(true) {}
PIProtectedVariable<T> & pv;
int counter = 0;
bool ownsLock = true;
};
//! \~english Replaces the protected value with \a v.

View File

@@ -37,3 +37,4 @@ pip_test(piobject)
pip_test(client_server pip_client_server)
pip_test(io)
pip_test(system)
pip_test(thread)

View File

@@ -0,0 +1,936 @@
//! \~\file piprotectedvariable_test.cpp
//! \~\brief Unit tests for PIProtectedVariable class
/*
PIP - Platform Independent Primitives
Unit tests for PIProtectedVariable
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pimutex.h"
#include "piprotectedvariable.h"
#include "gtest/gtest.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <memory>
#include <thread>
#include <vector>
using namespace std;
namespace {
constexpr int THREAD_COUNT = 10;
constexpr int ITERATIONS_PER_THREAD = 1000;
constexpr int WAIT_THREAD_TIME_MS = 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) {
PIProtectedVariable<int> pvInt;
PIProtectedVariable<double> pvDouble;
PIProtectedVariable<string> pvString;
pvInt.set(123);
pvDouble.set(3.14159);
pvString.set("Hello, World!");
EXPECT_EQ(pvInt.get(), 123);
EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
EXPECT_EQ(pvString.get(), "Hello, World!");
}
TEST(PIProtectedVariable_Basic, GetReturnsCopy) {
PIProtectedVariable<int> pv;
pv.set(100);
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, NUM_WRITER_ITERATIONS]() {
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) {
struct TestStruct {
int x = 10;
int y = 20;
int getValue() const { return x + y; }
};
PIProtectedVariable<TestStruct> pv;
pv.set(TestStruct());
auto ptr = pv.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");
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();
auto ptr2 = std::move(ptr1);
// ptr2 should have access
EXPECT_EQ(*ptr2, 42);
*ptr2 = 100;
EXPECT_EQ(pv.get(), 100);
}
TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) {
PIProtectedVariable<int> pv;
pv.set(42);
atomic<int> unlockCount(0);
// 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);
atomic<bool> deadlockOccurred(false);
// 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;
pv.set(42);
auto createAndMove = [&pv]() -> decltype(pv.getRef()) {
auto ptr = pv.getRef();
return std::move(ptr);
};
auto ptr = createAndMove();
*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
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
EXPECT_TRUE(true); // Placeholder assertion
}
//! \~\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();
}
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++;
}
}
});
}
// 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();
}
EXPECT_GT(totalReads, 0);
EXPECT_EQ(wrongReads, 0);
}