From 8ccc05ee785b9ce644eec05d40a631ea9d5e3280 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 17:33:27 +0300 Subject: [PATCH 01/15] simplify piprotectedvariable --- libs/main/thread/piprotectedvariable.h | 48 +- tests/CMakeLists.txt | 1 + tests/thread/piprotectedvariable_test.cpp | 936 ++++++++++++++++++++++ 3 files changed, 964 insertions(+), 21 deletions(-) create mode 100644 tests/thread/piprotectedvariable_test.cpp diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 5390fb5d..7c3f246c 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -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 . + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ #ifndef PIPROTECTEDVARIABLE_H @@ -39,16 +39,22 @@ public: //! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным. class PIP_EXPORT Pointer { friend class PIProtectedVariable; + 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 & v): pv(v) {} + Pointer(PIProtectedVariable & v): pv(v), ownsLock(true) {} PIProtectedVariable & pv; - int counter = 0; + bool ownsLock = true; }; //! \~english Replaces the protected value with \a v. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a5ba75e2..67f7d5f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,3 +37,4 @@ pip_test(piobject) pip_test(client_server pip_client_server) pip_test(io) pip_test(system) +pip_test(thread) diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp new file mode 100644 index 00000000..e9bdc1ff --- /dev/null +++ b/tests/thread/piprotectedvariable_test.cpp @@ -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 . +*/ + +#include "pimutex.h" +#include "piprotectedvariable.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +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 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) { + PIProtectedVariable pvInt; + PIProtectedVariable pvDouble; + PIProtectedVariable 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 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 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, 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 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) { + struct TestStruct { + int x = 10; + int y = 20; + int getValue() const { return x + y; } + }; + + PIProtectedVariable 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 pv; + pv.set("original"); + + 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(); + 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 pv; + pv.set(42); + + atomic 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 pv; + pv.set(42); + + atomic 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 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; + 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 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 + + 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 + + EXPECT_TRUE(true); // Placeholder assertion +} + +//! \~\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(); + } + 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++; + } + } + }); + } + + // 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); +} From ac877f10240f547b6d5e3e3dc264e208c19b4d7b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:13:23 +0300 Subject: [PATCH 02/15] fixes --- libs/main/thread/piprotectedvariable.h | 35 ++++++++++++++--------- main.cpp | 21 ++++++++++++++ tests/thread/piprotectedvariable_test.cpp | 20 ++++--------- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 7c3f246c..650d79f5 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -25,6 +25,7 @@ #ifndef PIPROTECTEDVARIABLE_H #define PIPROTECTEDVARIABLE_H +#include "picout.h" #include "pimutex.h" //! \~\ingroup Thread @@ -34,6 +35,8 @@ template class PIP_EXPORT PIProtectedVariable { public: + PIProtectedVariable(T v = T()): var(std::move(v)) {} + //! \~\brief //! \~english Pointer-like wrapper returned by \a getRef() while the protected value remains locked. //! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным. @@ -44,17 +47,23 @@ public: public: //! \~\english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other) noexcept: pv(other.pv), ownsLock(other.ownsLock) { other.ownsLock = false; } + Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; - //! \~\english Move assignment is deleted - Pointer can only be moved once. - //! \~russian Оператор перемещения удален - Pointer можно переместить только один раз. - Pointer & operator=(Pointer &&) = delete; + //! \~\english Move assignment - transfers ownership of the lock. + //! \~russian Оператор перемещения - передает владение блокировкой. + Pointer & operator=(Pointer && other) { + pv = other.pv; + other.can_unlock = false; + }; //! \~\english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. ~Pointer() { - if (ownsLock) pv.mutex.unlock(); + if (can_unlock) { + pv.mutex.unlock(); + piCout << "mutex.unlock()" << &(pv.mutex); + } } //! \~english Returns pointer access to the protected value. @@ -66,11 +75,14 @@ public: T & operator*() { return pv.var; } private: - Pointer() = delete; - Pointer(PIProtectedVariable & v): pv(v), ownsLock(true) {} + explicit Pointer() = delete; + explicit Pointer(PIProtectedVariable & v): pv(v) { + pv.mutex.lock(); + piCout << "mutex.lock()" << &(pv.mutex); + } PIProtectedVariable & pv; - bool ownsLock = true; + bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. @@ -82,10 +94,7 @@ public: //! \~english Locks the value and returns wrapper-based access to it. //! \~russian Блокирует значение и возвращает обертку для доступа к нему. - Pointer getRef() { - mutex.lock(); - return Pointer(*this); - } + Pointer getRef() { return Pointer(*this); } //! \~english Returns a copy of the protected value. //! \~russian Возвращает копию защищенного значения. @@ -104,7 +113,7 @@ public: private: mutable PIMutex mutex; - T var; + T var = {}; }; diff --git a/main.cpp b/main.cpp index cf2f6235..fd98c891 100644 --- a/main.cpp +++ b/main.cpp @@ -20,6 +20,27 @@ inline PIByteArray SMBusTypeInfo_genHash(PIString n) { int main(int argc, char * argv[]) { + PIProtectedVariable pv(3.0); + piCout << pv.get(); + { + auto ref = pv.getRef(); + piCout << *ref; + *ref = 11.; + piCout << *ref; + } + piCout << pv.get(); + { + auto ref = pv.getRef(); + piCout << *ref; + *ref = 12.; + piCout << *ref; + auto ref2 = std::move(ref); + piCout << *ref2; + } + piCout << pv.get(); + return 0; + + PICrypt _crypt; // auto ba = PIFile::readAll("logo.png"); PIString str = "hello!"_a; diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index e9bdc1ff..d0b15008 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -35,7 +34,6 @@ namespace { constexpr int THREAD_COUNT = 10; constexpr int ITERATIONS_PER_THREAD = 1000; -constexpr int WAIT_THREAD_TIME_MS = 1000; } // anonymous namespace @@ -229,7 +227,7 @@ TEST(PIProtectedVariable_ThreadSafety, MixedReadWriteOperations) { // Reader threads vector readers; for (int i = 0; i < THREAD_COUNT; ++i) { - readers.emplace_back([&pv, &readCount, &invalidReads, &start, NUM_WRITER_ITERATIONS]() { + readers.emplace_back([&pv, &readCount, &invalidReads, &start]() { while (!start.load()) { this_thread::yield(); } @@ -468,8 +466,6 @@ TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) { PIProtectedVariable pv; pv.set(42); - atomic 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 @@ -503,8 +499,6 @@ TEST(PIProtectedVariable_Move, SourceDoesNotUnlockAfterMove) { PIProtectedVariable pv; pv.set(42); - atomic deadlockOccurred(false); - // If source unlocked after move, we would get a double-unlock error // or undefined behavior. This test verifies that moving works correctly. @@ -569,15 +563,11 @@ TEST(PIProtectedVariable_Move, MoveWithRVODisabled) { // This test should be compiled with -fno-elide-constructors // to ensure move constructor is actually called - PIProtectedVariable pv; - pv.set(42); + PIProtectedVariable pv(42); - auto createAndMove = [&pv]() -> decltype(pv.getRef()) { - auto ptr = pv.getRef(); - return std::move(ptr); - }; - - auto ptr = createAndMove(); + auto ptr_src = pv.getRef(); + *ptr_src = 99; + auto ptr = std::move(ptr_src); *ptr = 100; EXPECT_EQ(pv.get(), 100); From 9f57f0107e96b351d54eb2f672791381c05b58fa Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:34:48 +0300 Subject: [PATCH 03/15] remove picout --- libs/main/thread/piprotectedvariable.h | 22 ++++++---------------- main.cpp | 4 ++-- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 650d79f5..754eb4e3 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -25,7 +25,6 @@ #ifndef PIPROTECTEDVARIABLE_H #define PIPROTECTEDVARIABLE_H -#include "picout.h" #include "pimutex.h" //! \~\ingroup Thread @@ -35,6 +34,8 @@ template class PIP_EXPORT PIProtectedVariable { public: + //! \~english Constructs %PIProtectedVariable and initialize variable by value `v`. + //! \~russian Создает %PIProtectedVariable и инициализирует переменную значением `v`. PIProtectedVariable(T v = T()): var(std::move(v)) {} //! \~\brief @@ -43,26 +44,18 @@ public: class PIP_EXPORT Pointer { friend class PIProtectedVariable; NO_COPY_CLASS(Pointer); + Pointer & operator=(Pointer && other) = delete; public: - //! \~\english Move constructor - transfers ownership of the lock. + //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; - - //! \~\english Move assignment - transfers ownership of the lock. - //! \~russian Оператор перемещения - передает владение блокировкой. - Pointer & operator=(Pointer && other) { - pv = other.pv; - other.can_unlock = false; - }; - - //! \~\english Destroys wrapper and releases the mutex. + //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. ~Pointer() { if (can_unlock) { pv.mutex.unlock(); - piCout << "mutex.unlock()" << &(pv.mutex); } } @@ -76,10 +69,7 @@ public: private: explicit Pointer() = delete; - explicit Pointer(PIProtectedVariable & v): pv(v) { - pv.mutex.lock(); - piCout << "mutex.lock()" << &(pv.mutex); - } + explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; bool can_unlock = true; diff --git a/main.cpp b/main.cpp index fd98c891..7dda6513 100644 --- a/main.cpp +++ b/main.cpp @@ -23,9 +23,9 @@ int main(int argc, char * argv[]) { PIProtectedVariable pv(3.0); piCout << pv.get(); { - auto ref = pv.getRef(); + auto ref = pv.getRef(); piCout << *ref; - *ref = 11.; + *ref = 11.; piCout << *ref; } piCout << pv.get(); From fe01c353e681e87c0513b02e0b2f0f9b0c91dfce Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:56:20 +0300 Subject: [PATCH 04/15] simplify tests --- tests/thread/piprotectedvariable_test.cpp | 926 ++-------------------- 1 file changed, 68 insertions(+), 858 deletions(-) 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); } From 449978bda013e6d926b8d91b1225e464f28fa6c0 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:07:01 +0300 Subject: [PATCH 05/15] revert main and more tests --- main.cpp | 21 --------- tests/thread/piprotectedvariable_test.cpp | 56 ++++++++--------------- 2 files changed, 18 insertions(+), 59 deletions(-) diff --git a/main.cpp b/main.cpp index 7dda6513..cf2f6235 100644 --- a/main.cpp +++ b/main.cpp @@ -20,27 +20,6 @@ inline PIByteArray SMBusTypeInfo_genHash(PIString n) { int main(int argc, char * argv[]) { - PIProtectedVariable pv(3.0); - piCout << pv.get(); - { - auto ref = pv.getRef(); - piCout << *ref; - *ref = 11.; - piCout << *ref; - } - piCout << pv.get(); - { - auto ref = pv.getRef(); - piCout << *ref; - *ref = 12.; - piCout << *ref; - auto ref2 = std::move(ref); - piCout << *ref2; - } - piCout << pv.get(); - return 0; - - PICrypt _crypt; // auto ba = PIFile::readAll("logo.png"); PIString str = "hello!"_a; diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index 09f52d05..77c43c05 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -1,32 +1,11 @@ -//! \~\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 . -*/ - #include "piprotectedvariable.h" #include "pistring.h" #include "pithread.h" +#include "piliterals_time.h" #include "gtest/gtest.h" #include -using namespace std; - // Basic functionality tests TEST(PIProtectedVariable_Basic, BasicFunctionality) { // Test basic set/get with different types @@ -65,22 +44,30 @@ TEST(PIProtectedVariable_Basic, BasicFunctionality) { ptr->x = 100; EXPECT_EQ(pvStruct.get().x, 100); - // Test move semantics for Pointer + // Test for Pointer pvInt.set(42); auto ptr1 = pvInt.getRef(); + EXPECT_EQ(*ptr1, 42); + *ptr1 = 55; + EXPECT_EQ(*ptr1, 55); auto ptr2 = std::move(ptr1); - EXPECT_EQ(*ptr2, 42); + EXPECT_EQ(*ptr2, 55); *ptr2 = 100; - EXPECT_EQ(pvInt.get(), 100); + EXPECT_EQ(*ptr2, 100); + auto ptr3 = pvInt.getRef(); + EXPECT_EQ(*ptr3, 100); + *ptr3 = 333; + EXPECT_EQ(*ptr3, 333); + EXPECT_EQ(pvInt.get(), 333); } // Thread safety tests TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { PIProtectedVariable pv; - atomic writeCount(0); - atomic readCount(0); - atomic invalidReads(0); + std::atomic writeCount(0); + std::atomic readCount(0); + std::atomic invalidReads(0); const int NUM_ITERATIONS = 1000; const int NUM_WRITERS = 10; const int NUM_READERS = 20; @@ -114,16 +101,9 @@ TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { })); } - // Start all threads - for (auto * t: threads) { - t->startOnce(); - } - - // Wait for all threads to finish - for (auto * t: threads) { - t->waitForFinish(PISystemTime::fromSeconds(5)); - delete t; - } + threads.forEach([](PIThread * & t) {t->startOnce();}); + threads.forEach([](PIThread * & t) {t->waitForFinish(2_s);}); + piDeleteAll(threads); // Verify results EXPECT_EQ(writeCount, TOTAL_WRITES); From e761625eab90641cdfee280528665322d481f1c4 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:14:14 +0300 Subject: [PATCH 06/15] using recursive mutex --- libs/main/thread/piprotectedvariable.h | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 754eb4e3..077d2192 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -49,15 +49,11 @@ public: public: //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; + Pointer(Pointer && other): pv(other.pv) { pv.mutex.lock(); }; //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. - ~Pointer() { - if (can_unlock) { - pv.mutex.unlock(); - } - } + ~Pointer() { pv.mutex.unlock(); } //! \~english Returns pointer access to the protected value. //! \~russian Возвращает указательный доступ к защищенному значению. @@ -72,7 +68,6 @@ public: explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; - bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. From 9dc1af921c100ebbba50feafd2f59903fec7eafc Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:18:44 +0300 Subject: [PATCH 07/15] more simplify Pointer --- tests/thread/piprotectedvariable_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index 77c43c05..e7facc40 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -82,7 +82,9 @@ TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { for (int j = 0; j < NUM_ITERATIONS; ++j) { auto val = pv.getRef(); (*val)++; + auto val2 = pv.getRef(); writeCount++; + ASSERT_EQ(writeCount, *val2); } })); } From c02b627d470e47a7435bace0accbeb4158ee8bae Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 20:14:22 +0300 Subject: [PATCH 08/15] some fixes PIEthernet::listen PIClientServer::Server::~Server PISystemTime const sleep ClientServer tests fast and stable --- libs/client_server/piclientserver_server.cpp | 6 +---- libs/main/io_devices/piethernet.cpp | 1 + libs/main/types/pisystemtime.cpp | 4 ++-- libs/main/types/pisystemtime.h | 4 ++-- tests/client_server/client_server_test.cpp | 24 +++++++------------- 5 files changed, 14 insertions(+), 25 deletions(-) diff --git a/libs/client_server/piclientserver_server.cpp b/libs/client_server/piclientserver_server.cpp index f1700b01..edfd2083 100644 --- a/libs/client_server/piclientserver_server.cpp +++ b/libs/client_server/piclientserver_server.cpp @@ -72,11 +72,7 @@ PIClientServer::Server::~Server() { clean_thread->waitForFinish(); piDeleteSafety(clean_thread); stopServer(); - for (auto c: clients) { - c->aboutDelete(); - c->destroy(); - delete c; - } + closeAll(); piDeleteSafety(tcp_server); } diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 19eddf28..7cb7a1dc 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -527,6 +527,7 @@ bool PIEthernet::listen(bool threaded) { listen_threaded = true; server_bounded = false; server_thread_.start(server_func); + server_thread_.waitForStart(); return true; } listen_threaded = server_bounded = false; diff --git a/libs/main/types/pisystemtime.cpp b/libs/main/types/pisystemtime.cpp index 4d218968..f26b7bd6 100644 --- a/libs/main/types/pisystemtime.cpp +++ b/libs/main/types/pisystemtime.cpp @@ -105,7 +105,7 @@ extern clock_serv_t __pi_mac_clock; //! Используйте этот метод для ожидания разниц системных времен или своего времени. //! Если метод будет вызван для системного времени \a PISystemTime::current(), то //! ожидание будет почти бесконечным -void PISystemTime::sleep() { +void PISystemTime::sleep() const { piUSleep(piFloord(toMicroseconds())); } @@ -118,7 +118,7 @@ void PISystemTime::toTimespec(void * ts) { } -PISystemTime::Frequency PISystemTime::toFrequency() { +PISystemTime::Frequency PISystemTime::toFrequency() const { return PISystemTime::Frequency::fromSystemTime(*this); } diff --git a/libs/main/types/pisystemtime.h b/libs/main/types/pisystemtime.h index bb197377..c7438c7e 100644 --- a/libs/main/types/pisystemtime.h +++ b/libs/main/types/pisystemtime.h @@ -253,7 +253,7 @@ public: //! \~english Sleep for this time //! \~russian Ожидать это время - void sleep(); + void sleep() const; //! \~english On *nix system assign current value to timespec struct //! \~russian На *nix системах присваивает время к timespec структуре @@ -261,7 +261,7 @@ public: //! \~english Returns \a Frequency that corresponds this time interval //! \~russian Возвращает \a Frequency соответствующую этому временному интервалу - PISystemTime::Frequency toFrequency(); + PISystemTime::Frequency toFrequency() const; //! \~english Returns "yyyy-MM-dd hh:mm:ss.zzz" for absolute time and " ..." for relative //! \~russian Возвращает "yyyy-MM-dd hh:mm:ss.zzz" для абсолютного времени и " ..." для относительного diff --git a/tests/client_server/client_server_test.cpp b/tests/client_server/client_server_test.cpp index bbe639c5..e0980649 100644 --- a/tests/client_server/client_server_test.cpp +++ b/tests/client_server/client_server_test.cpp @@ -32,6 +32,7 @@ Client * createAndConnectClient() { TEST(ClientServer, OneClient) { auto const loop_timeout = 1000_ms; auto s = createServer(); + piMinSleep(); auto c = createAndConnectClient>(); waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); @@ -106,6 +107,7 @@ TEST(ClientServer, ManyClients) { constexpr int clients_count = 20; PIVector clients; auto s = createServer(); + piMinSleep(); piForTimes(clients_count) { clients.append(new ClientSendThread()); @@ -137,7 +139,7 @@ TEST(ClientServer, ManyClients) { for (const auto c: clients) { c->startSend(); } - (100_ms).sleep(); + loop_timeout.sleep(); EXPECT_TRUE(getClientsPings(clients) > clients_count * 2); EXPECT_TRUE(getServerPongs(s) > clients_count * 2); piDeleteAllAndClear(clients); @@ -147,7 +149,7 @@ TEST(ClientServer, ManyClients) { } TEST(ClientServer, DynamicClients) { - auto const loop_timeout = 100_ms; + auto const loop_timeout = 10_ms; constexpr int clients_count = 20; PIVector clients; PIMutex clients_mutex; @@ -160,7 +162,6 @@ TEST(ClientServer, DynamicClients) { clients_mutex.lock(); clients << c; clients_mutex.unlock(); - piCout << "new client" << clients.size(); }; piForTimes(clients_count) { @@ -178,9 +179,8 @@ TEST(ClientServer, DynamicClients) { piForTimes(new_cnt) { spawnClient(); } - piCout << "+++++++"; }, - 12_Hz); + 120_Hz); deleteThread.start( [&clients, &clients_mutex]() { @@ -194,28 +194,20 @@ TEST(ClientServer, DynamicClients) { clients_mutex.unlock(); if (c) { delete c; - piCout << "remove client" << clients.size(); } } - piCout << "----------"; }, - 13_Hz); + 130_Hz); - (2_s).sleep(); + (loop_timeout * clients_count).sleep(); EXPECT_GE(s->clientsCount(), 10); - piCout << "now clients" << clients.size(); - - deleteThread.stopAndWait(); spawnThread.stopAndWait(); - - piCout << "total clients" << clients.size(); - piDeleteAllAndClear(clients); - waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout * clients_count); EXPECT_EQ(0, s->clientsCount()); delete s; From ac415ebbb6a06b137b1a903f4fc58333d9eac01b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 08:54:06 +0300 Subject: [PATCH 09/15] revert can_unlock flag --- libs/main/thread/piprotectedvariable.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 077d2192..754eb4e3 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -49,11 +49,15 @@ public: public: //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other): pv(other.pv) { pv.mutex.lock(); }; + Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. - ~Pointer() { pv.mutex.unlock(); } + ~Pointer() { + if (can_unlock) { + pv.mutex.unlock(); + } + } //! \~english Returns pointer access to the protected value. //! \~russian Возвращает указательный доступ к защищенному значению. @@ -68,6 +72,7 @@ public: explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; + bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. From ccbf86f781902dbe96c1133933054919610bf8d6 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 09:30:28 +0300 Subject: [PATCH 10/15] disable exeptions in cmake --- 3rd/LuaBridge/detail/LuaException.h | 9 ++++++--- 3rd/LuaBridge/detail/Namespace.h | 9 ++++++--- 3rd/LuaBridge/detail/Userdata.h | 7 +++++-- CMakeLists.txt | 2 +- libs/main/core/pibase.h | 15 +++++++-------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/3rd/LuaBridge/detail/LuaException.h b/3rd/LuaBridge/detail/LuaException.h index 836cb852..2fe6d608 100644 --- a/3rd/LuaBridge/detail/LuaException.h +++ b/3rd/LuaBridge/detail/LuaException.h @@ -97,8 +97,10 @@ public: { int code = lua_pcall (L, nargs, nresults, msgh); - if (code != LUABRIDGE_LUA_OK) - Throw (LuaException (L, code)); + if (code != LUABRIDGE_LUA_OK) { + // Throw (LuaException (L, code)); + assert(true); + } } //---------------------------------------------------------------------------- @@ -128,7 +130,8 @@ protected: private: static int throwAtPanic (lua_State* L) { - throw LuaException (L, -1); + // throw LuaException (L, -1); + return -1; } }; diff --git a/3rd/LuaBridge/detail/Namespace.h b/3rd/LuaBridge/detail/Namespace.h index 68ae21fa..8a4dd20a 100644 --- a/3rd/LuaBridge/detail/Namespace.h +++ b/3rd/LuaBridge/detail/Namespace.h @@ -101,7 +101,8 @@ protected: { if (m_stackSize == 0) { - throw std::logic_error ("Unable to continue registration"); + std::cerr << ("Unable to continue registration"); + assert(true); } } }; @@ -1054,7 +1055,8 @@ public: { if (m_stackSize == 1) { - throw std::logic_error ("endNamespace () called on global namespace"); + std::cerr << ("endNamespace () called on global namespace"); + assert(true); } assert (m_stackSize > 1); @@ -1150,7 +1152,8 @@ public: { if (m_stackSize == 1) { - throw std::logic_error ("addProperty () called on global namespace"); + std::cerr << ("addProperty () called on global namespace"); + assert(true); } assert (lua_istable (L, -1)); // Stack: namespace table (ns) diff --git a/3rd/LuaBridge/detail/Userdata.h b/3rd/LuaBridge/detail/Userdata.h index ef13d9fc..3993e176 100644 --- a/3rd/LuaBridge/detail/Userdata.h +++ b/3rd/LuaBridge/detail/Userdata.h @@ -33,6 +33,7 @@ #include #include +#include namespace luabridge { @@ -320,7 +321,8 @@ public: lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo ::getClassKey ()); if (!lua_istable (L, -1)) { - throw std::logic_error ("The class is not registered in LuaBridge"); + std::cerr << ("The class is not registered in LuaBridge"); + assert(true); } lua_setmetatable (L, -2); return ud; @@ -375,7 +377,8 @@ private: lua_rawgetp (L, LUA_REGISTRYINDEX, key); if (!lua_istable (L, -1)) { - throw std::logic_error ("The class is not registered in LuaBridge"); + std::cerr << ("The class is not registered in LuaBridge"); + assert(true); } lua_setmetatable (L, -2); } diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e4740bd..8478147f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,7 +363,7 @@ if(WIN32) set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0") endif() else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-exceptions") if(DEFINED ENV{QNX_HOST} OR PIP_FREERTOS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-32") endif() diff --git a/libs/main/core/pibase.h b/libs/main/core/pibase.h index 8f6223c8..b379dea4 100644 --- a/libs/main/core/pibase.h +++ b/libs/main/core/pibase.h @@ -726,6 +726,7 @@ inline bool piDeleteSafety(T *& pointer) { //! \~english In this example "Error!" will be printed on every \b false function return. //! \~russian В данном примере будет выведен "Error!" при каждом \b false возврате из функции. class PIP_EXPORT PIScopeExitCall { + NO_COPY_CLASS(PIScopeExitCall) public: //! \~\brief //! \~english Constructor that takes a function to execute @@ -758,8 +759,6 @@ public: } private: - NO_COPY_CLASS(PIScopeExitCall) - std::function func; }; @@ -768,14 +767,14 @@ private: //! \~english Inherit from this class to make your class non-trivially copyable. //! \~russian Наследуйтесь от этого класса чтобы сделать свой класс нетривиально копируемым. struct PIP_EXPORT PINonTriviallyCopyable { - PINonTriviallyCopyable() noexcept = default; - PINonTriviallyCopyable(const PINonTriviallyCopyable &) noexcept = default; - PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept; - PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) noexcept = default; - PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) noexcept = default; + PINonTriviallyCopyable() = default; + PINonTriviallyCopyable(const PINonTriviallyCopyable &) = default; + PINonTriviallyCopyable(PINonTriviallyCopyable &&) ; + PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) = default; + PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) = default; ~PINonTriviallyCopyable() = default; }; -inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept = default; +inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) = default; //! \~\brief From ba57aa0144522959166bb3bf92a47162d575b88e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 09:40:32 +0300 Subject: [PATCH 11/15] disable exceptions on win --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8478147f..19d29932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -360,7 +360,7 @@ endif() if(WIN32) add_definitions(-DPSAPI_VERSION=1) if(${C_COMPILER} STREQUAL "cl.exe") - set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0") + set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0 /EH-") endif() else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-exceptions") From 79fa5492017dc278696fe9f61cdeadf0957361c8 Mon Sep 17 00:00:00 2001 From: peri4 Date: Wed, 18 Mar 2026 11:46:08 +0300 Subject: [PATCH 12/15] fix matrix test --- tests/math/testpimathmatrixt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/math/testpimathmatrixt.cpp b/tests/math/testpimathmatrixt.cpp index 0245599c..243431ec 100644 --- a/tests/math/testpimathmatrixt.cpp +++ b/tests/math/testpimathmatrixt.cpp @@ -376,7 +376,7 @@ TEST(PIMathMatrixT_Test, invert) { matrix3 = matrix1; matrix1.invert(); EXPECT_EQ(matrix1, matrix3); - EXPECT_DOUBLE_EQ(std::abs(d1 - (1. / d2)), 0.); + EXPECT_NEAR(std::abs(d1 - (1. / d2)), 0., 1e-12); } TEST(PIMathMatrixT_Test, inverted) { From 99c99c39c258b663cb103152eb3099e58790ac06 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Mar 2026 13:39:01 +0300 Subject: [PATCH 13/15] separate cmake opts for tests --- CMakeLists.txt | 6 +++++- tests/CMakeLists.txt | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19d29932..eaec1c29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,8 @@ set(PIP_DLL_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE STRING "") option(ICU "ICU support for convert codepages" ${_ICU_DEFAULT}) option(STD_IOSTREAM "Building with std iostream operators support" OFF) option(INTROSPECTION "Build with introspection" OFF) -option(TESTS "Build tests and perform their before install step" OFF) +option(TESTS "Build tests" OFF) +option(TESTS_RUN "Run tests before install step" OFF) option(COVERAGE "Build project with coverage info" OFF) option(PIP_FFTW_F "Support fftw module for float" ON) option(PIP_FFTW_L "Support fftw module for long double" ON) @@ -823,6 +824,9 @@ if (PIP_TESTS_LIST) foreach(_test ${PIP_TESTS_LIST}) message(" * ${_test}") endforeach() + if (TESTS_RUN) + message("TESTS_RUN ON -> Run tests before install step") + endif() else() message(" Tests: skip (tests off)") endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67f7d5f5..20503839 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,8 +24,10 @@ macro(pip_test NAME) file(GLOB _HDRS "${NAME}/*.h") set(_target pip_${NAME}_test) add_executable(${_target} ${_CPPS} ${_HDRS}) - target_link_libraries(${_target} pip ${ARGN} gtest_main) - gtest_discover_tests(${_target}) + target_link_libraries(${_target} pip ${ARGN} gtest_main) + if (TESTS_RUN) + gtest_discover_tests(${_target}) + endif() list(APPEND PIP_TESTS_LIST "${NAME}") set(PIP_TESTS_LIST ${PIP_TESTS_LIST} PARENT_SCOPE) endmacro() From a1be5be5a1ae7b2c2944e860f1d00a532403afe3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Mar 2026 15:01:17 +0300 Subject: [PATCH 14/15] increase timeout and remove sleep --- tests/client_server/client_server_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/client_server/client_server_test.cpp b/tests/client_server/client_server_test.cpp index e0980649..cb6fc68c 100644 --- a/tests/client_server/client_server_test.cpp +++ b/tests/client_server/client_server_test.cpp @@ -33,7 +33,7 @@ TEST(ClientServer, OneClient) { auto const loop_timeout = 1000_ms; auto s = createServer(); piMinSleep(); - auto c = createAndConnectClient>(); + auto c = createAndConnectClient>(); waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); EXPECT_EQ(1, s->clientsCount()); @@ -103,7 +103,7 @@ int getClientsPings(const PIVector & clients) { TEST(ClientServer, ManyClients) { - auto const loop_timeout = 100_ms; + auto const loop_timeout = 1_s; constexpr int clients_count = 20; PIVector clients; auto s = createServer(); @@ -139,8 +139,9 @@ TEST(ClientServer, ManyClients) { for (const auto c: clients) { c->startSend(); } - loop_timeout.sleep(); + waitLoop([&clients]() { return getClientsPings(clients) > clients_count * 2; }, loop_timeout); EXPECT_TRUE(getClientsPings(clients) > clients_count * 2); + waitLoop([s]() { return getServerPongs(s) > clients_count * 2; }, loop_timeout); EXPECT_TRUE(getServerPongs(s) > clients_count * 2); piDeleteAllAndClear(clients); waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); @@ -149,7 +150,7 @@ TEST(ClientServer, ManyClients) { } TEST(ClientServer, DynamicClients) { - auto const loop_timeout = 10_ms; + auto const loop_timeout = 3_s; constexpr int clients_count = 20; PIVector clients; PIMutex clients_mutex; @@ -199,15 +200,14 @@ TEST(ClientServer, DynamicClients) { }, 130_Hz); - (loop_timeout * clients_count).sleep(); - + waitLoop([s]() { return s->clientsCount() >= 10; }, loop_timeout); EXPECT_GE(s->clientsCount(), 10); deleteThread.stopAndWait(); spawnThread.stopAndWait(); piDeleteAllAndClear(clients); - waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout * clients_count); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); EXPECT_EQ(0, s->clientsCount()); delete s; From fe3b30bd49d21f6ffa9f93c63983b78e0c062f8f Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 20 Mar 2026 13:00:24 +0300 Subject: [PATCH 15/15] version 5.6.1 patch deploy_tool: procDpkg now prioritize "non-cross" and "non-dev" packages, then only "non-cross" add PIP_MANUAL_TEST CMake option --- CMakeLists.txt | 17 ++++++++++------- utils/deploy_tool/main.cpp | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaec1c29..de404754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ endif() project(PIP) set(PIP_MAJOR 5) set(PIP_MINOR 6) -set(PIP_REVISION 0) +set(PIP_REVISION 1) set(PIP_SUFFIX ) set(PIP_COMPANY SHS) set(PIP_DOMAIN org.SHS) @@ -73,6 +73,7 @@ option(COVERAGE "Build project with coverage info" OFF) option(PIP_FFTW_F "Support fftw module for float" ON) option(PIP_FFTW_L "Support fftw module for long double" ON) option(PIP_FFTW_Q "Support fftw module for quad double" OFF) +option(PIP_MANUAL_TEST "Build dev test (main.cpp)" OFF) set(PIP_UTILS 1) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD 11) @@ -593,12 +594,14 @@ if (NOT CROSSTOOLS) #target_link_libraries(pip_plugin pip) if (NOT DEFINED ANDROID_PLATFORM) - if(microhttpd_FOUND AND curl_FOUND) - add_executable(pip_test "main.cpp") - target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server pip_http_client) - if(sodium_FOUND) - add_executable(pip_cloud_test "main_picloud_test.cpp") - target_link_libraries(pip_cloud_test pip_cloud) + if (PIP_MANUAL_TEST) + if(microhttpd_FOUND AND curl_FOUND) + add_executable(pip_test "main.cpp") + target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server pip_http_client) + if(sodium_FOUND) + add_executable(pip_cloud_test "main_picloud_test.cpp") + target_link_libraries(pip_cloud_test pip_cloud) + endif() endif() endif() endif() diff --git a/utils/deploy_tool/main.cpp b/utils/deploy_tool/main.cpp index 899c9a46..69b15942 100644 --- a/utils/deploy_tool/main.cpp +++ b/utils/deploy_tool/main.cpp @@ -467,11 +467,20 @@ bool procDpkg(const PIString & l) { if (!vs.isEmpty()) { PIStringList lines = vs.split('\n').reverse(); for (auto l: lines) { - auto sl = l; - l = l.left(l.find(":")); - if (!l.isEmpty() && !l.endsWith("-cross") && !l.contains(' ')) all_deps << l; - // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; - return true; + l = l.left(l.find(":")); + if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross") && !l.endsWith("-dev")) { + // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; + all_deps << l; + return true; + } + } + for (auto l: lines) { + l = l.left(l.find(":")); + if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross")) { + // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; + all_deps << l; + return true; + } } } // piCout << "No dep on" << l;