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