diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index d0b15008..09f52d05 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -18,909 +18,119 @@ along with this program. If not, see . */ -#include "pimutex.h" #include "piprotectedvariable.h" +#include "pistring.h" +#include "pithread.h" #include "gtest/gtest.h" #include -#include -#include -#include -#include 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 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 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 pvInt; PIProtectedVariable pvDouble; - PIProtectedVariable pvString; + PIProtectedVariable 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 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 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 pv; - pv = 999; - EXPECT_EQ(pv.get(), 999); -} - -TEST(PIProtectedVariable_Basic, OperatorAssignmentReturnsReference) { - PIProtectedVariable pv; - PIProtectedVariable & 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 pv; - pv.set(0); // Set to zero explicitly - EXPECT_EQ(pv.get(), 0); - - PIProtectedVariable pvStr; - pvStr.set(""); // Set to empty explicitly - EXPECT_TRUE(pvStr.get().empty()); -} - -TEST(PIProtectedVariable_Basic, SetWithMoveSemantics) { - PIProtectedVariable 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 pv; - atomic start(false); - - vector 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 pv; - pv.set(42); - atomic readCount(0); - atomic wrongCount(0); - atomic start(false); - - vector 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 pv; - pv.set(0); // Initialize with a valid value - atomic counter(0); - atomic readCount(0); - atomic invalidReads(0); - atomic 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 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 pv; - pv.set(0); // Initialize with a valid value - atomic writeCount(0); - atomic readCount(0); - atomic done(false); - atomic start(false); - - // Multiple writers - vector 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 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 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 pv; - pv.set(TestStruct()); + PIProtectedVariable 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 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 pv; - pv.set(100); - - auto ptr = pv.getRef(); - (*ptr) += 50; - - EXPECT_EQ(pv.get(), 150); -} - -TEST(PIProtectedVariable_Pointer, MutexLockedWhenPointerCreated) { - PIProtectedVariable pv; - pv.set(42); - - atomic gotRef(false); - atomic 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 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 pv; - pv.set("Hello"); - - { - auto ptr = pv.getRef(); - *ptr += " World"; - } - - EXPECT_EQ(pv.get(), "Hello World"); -} - -TEST(PIProtectedVariable_Pointer, PointerWithComplexTypes) { - struct ComplexType { - vector data; - map mapping; - - ComplexType() { - data = {1, 2, 3, 4, 5}; - mapping["one"] = 1; - mapping["two"] = 2; - } - }; - - PIProtectedVariable 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 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 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 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 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 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 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 data; - string name; - - ComplexType(): data{1, 2, 3}, name("test") {} - }; - - PIProtectedVariable 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 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 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 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 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 writeCount(0); + atomic readCount(0); + atomic 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 threads; -//! \~\brief Edge case tests -//! \~\english Tests for edge cases and exception safety -//! \~\russian Тесты граничных случаев и исключительной безопасности -TEST(PIProtectedVariable_EdgeCases, MultipleGetRefCallsSequential) { - PIProtectedVariable 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 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 pv; - atomic counter(0); - atomic start(false); - const int NUM_THREADS = 50; - - vector 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 pvInt; - PIProtectedVariable pvDouble; - PIProtectedVariable pvBool; - PIProtectedVariable pvString; - PIProtectedVariable 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 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 pvStr; - PIProtectedVariable> pvVec; - - pvStr.set(""); - EXPECT_TRUE(pvStr.get().empty()); - - pvVec.set(vector()); - EXPECT_TRUE(pvVec.get().empty()); -} - -TEST(PIProtectedVariable_EdgeCases, PointerLifetime) { - PIProtectedVariable 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 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 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 buffer; - atomic done(false); - atomic produced(0); - atomic consumed(0); - atomic 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 config; - config.set(100); - - atomic totalReads(0); - atomic wrongReads(0); - atomic start(false); - atomic done(false); - - // Readers - vector 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); }