simplify tests
This commit is contained in:
@@ -18,909 +18,119 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "pimutex.h"
|
|
||||||
#include "piprotectedvariable.h"
|
#include "piprotectedvariable.h"
|
||||||
|
#include "pistring.h"
|
||||||
|
#include "pithread.h"
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
|
||||||
#include <climits>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace {
|
// Basic functionality tests
|
||||||
|
TEST(PIProtectedVariable_Basic, BasicFunctionality) {
|
||||||
constexpr int THREAD_COUNT = 10;
|
// Test basic set/get with different types
|
||||||
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) {
|
|
||||||
PIProtectedVariable<int> pvInt;
|
PIProtectedVariable<int> pvInt;
|
||||||
PIProtectedVariable<double> pvDouble;
|
PIProtectedVariable<double> pvDouble;
|
||||||
PIProtectedVariable<string> pvString;
|
PIProtectedVariable<PIString> pvString;
|
||||||
|
|
||||||
pvInt.set(123);
|
pvInt.set(123);
|
||||||
pvDouble.set(3.14159);
|
pvDouble.set(3.14159);
|
||||||
pvString.set("Hello, World!");
|
pvString.set(PIString("Hello, World!"));
|
||||||
|
|
||||||
EXPECT_EQ(pvInt.get(), 123);
|
EXPECT_EQ(pvInt.get(), 123);
|
||||||
EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
|
EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
|
||||||
EXPECT_EQ(pvString.get(), "Hello, World!");
|
EXPECT_EQ(pvString.get(), PIString("Hello, World!"));
|
||||||
}
|
|
||||||
|
|
||||||
TEST(PIProtectedVariable_Basic, GetReturnsCopy) {
|
// Test operator=
|
||||||
PIProtectedVariable<int> pv;
|
pvInt = 999;
|
||||||
pv.set(100);
|
EXPECT_EQ(pvInt.get(), 999);
|
||||||
|
|
||||||
int val1 = pv.get();
|
// Test getRef() with Pointer
|
||||||
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) {
|
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
int x = 10;
|
int x = 10;
|
||||||
int y = 20;
|
int y = 20;
|
||||||
int getValue() const { return x + y; }
|
int getValue() const { return x + y; }
|
||||||
};
|
};
|
||||||
|
|
||||||
PIProtectedVariable<TestStruct> pv;
|
PIProtectedVariable<TestStruct> pvStruct;
|
||||||
pv.set(TestStruct());
|
|
||||||
|
|
||||||
auto ptr = pv.getRef();
|
auto ptr = pvStruct.getRef();
|
||||||
EXPECT_EQ(ptr->x, 10);
|
EXPECT_EQ(ptr->x, 10);
|
||||||
EXPECT_EQ(ptr->y, 20);
|
EXPECT_EQ(ptr->y, 20);
|
||||||
EXPECT_EQ(ptr->getValue(), 30);
|
EXPECT_EQ(ptr->getValue(), 30);
|
||||||
}
|
|
||||||
|
|
||||||
TEST(PIProtectedVariable_Pointer, OperatorDerefWorks) {
|
// Modify through pointer
|
||||||
PIProtectedVariable<string> pv;
|
*ptr = TestStruct();
|
||||||
pv.set("original");
|
ptr->x = 100;
|
||||||
|
EXPECT_EQ(pvStruct.get().x, 100);
|
||||||
|
|
||||||
auto ptr = pv.getRef();
|
// Test move semantics for Pointer
|
||||||
*ptr = "modified";
|
pvInt.set(42);
|
||||||
|
auto ptr1 = pvInt.getRef();
|
||||||
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);
|
auto ptr2 = std::move(ptr1);
|
||||||
|
|
||||||
// ptr2 should have access
|
|
||||||
EXPECT_EQ(*ptr2, 42);
|
EXPECT_EQ(*ptr2, 42);
|
||||||
*ptr2 = 100;
|
*ptr2 = 100;
|
||||||
EXPECT_EQ(pv.get(), 100);
|
EXPECT_EQ(pvInt.get(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) {
|
// Thread safety tests
|
||||||
PIProtectedVariable<int> pv;
|
TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) {
|
||||||
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
|
|
||||||
|
|
||||||
PIProtectedVariable<int> pv;
|
PIProtectedVariable<int> pv;
|
||||||
|
|
||||||
// The fact that this file compiles proves that:
|
atomic<int> writeCount(0);
|
||||||
// 1. Pointer copy constructor is deleted
|
atomic<int> readCount(0);
|
||||||
// 2. Pointer copy assignment is deleted
|
atomic<int> invalidReads(0);
|
||||||
// 3. Pointer move assignment is deleted
|
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
|
// Create writer threads
|
||||||
//! \~\english Tests for edge cases and exception safety
|
for (int i = 0; i < NUM_WRITERS; ++i) {
|
||||||
//! \~\russian Тесты граничных случаев и исключительной безопасности
|
threads.push_back(new PIThread([&pv, &writeCount]() {
|
||||||
TEST(PIProtectedVariable_EdgeCases, MultipleGetRefCallsSequential) {
|
for (int j = 0; j < NUM_ITERATIONS; ++j) {
|
||||||
PIProtectedVariable<int> pv;
|
auto val = pv.getRef();
|
||||||
pv.set(0);
|
(*val)++;
|
||||||
|
writeCount++;
|
||||||
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);
|
// Create reader threads
|
||||||
for (auto & t: threads) {
|
for (int i = 0; i < NUM_READERS; ++i) {
|
||||||
t.join();
|
threads.push_back(new PIThread([&pv, &invalidReads, &readCount]() {
|
||||||
}
|
for (int j = 0; j < NUM_ITERATIONS; ++j) {
|
||||||
|
auto val = pv.get();
|
||||||
EXPECT_EQ(counter, NUM_THREADS * 100);
|
readCount++;
|
||||||
}
|
// Value should always be in valid range [0, TOTAL_WRITES]
|
||||||
|
if (val < 0 || val > TOTAL_WRITES) {
|
||||||
TEST(PIProtectedVariable_EdgeCases, ValueInitializationOfDifferentTypes) {
|
invalidReads++;
|
||||||
// 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
|
// Start all threads
|
||||||
thread writer([&]() {
|
for (auto * t: threads) {
|
||||||
while (!start.load()) {
|
t->startOnce();
|
||||||
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);
|
// Wait for all threads to finish
|
||||||
EXPECT_EQ(wrongReads, 0);
|
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