16 Commits

Author SHA1 Message Date
ba63e72bfa Merge pull request 'disable exeptions in cmake' (#199) from disable_exeptions into master
Reviewed-on: #199
2026-03-18 11:48:54 +03:00
79fa549201 fix matrix test 2026-03-18 11:46:08 +03:00
35140ee002 Merge pull request 'some fixes' (#198) from some_fixes into master
Reviewed-on: #198
2026-03-18 11:08:48 +03:00
f0c8bfef0a Merge pull request 'simplify piprotectedvariable' (#197) from protected_var_refact into master
Reviewed-on: #197
2026-03-18 10:58:58 +03:00
ba57aa0144 disable exceptions on win 2026-03-18 09:40:32 +03:00
ccbf86f781 disable exeptions in cmake 2026-03-18 09:30:28 +03:00
ac415ebbb6 revert can_unlock flag 2026-03-18 08:54:06 +03:00
c02b627d47 some fixes
PIEthernet::listen
PIClientServer::Server::~Server
PISystemTime const sleep
ClientServer tests fast and stable
2026-03-17 20:14:22 +03:00
9dc1af921c more simplify Pointer 2026-03-17 19:18:44 +03:00
e761625eab using recursive mutex 2026-03-17 19:14:14 +03:00
449978bda0 revert main and more tests 2026-03-17 19:07:01 +03:00
fe01c353e6 simplify tests 2026-03-17 18:56:20 +03:00
9f57f0107e remove picout 2026-03-17 18:34:48 +03:00
ac877f1024 fixes 2026-03-17 18:13:23 +03:00
8ccc05ee78 simplify piprotectedvariable 2026-03-17 17:33:27 +03:00
2798d7de9c fix gitignore 2026-03-17 09:33:10 +03:00
15 changed files with 193 additions and 72 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
/src_main/piversion.h /src_main/piversion.h
/.svn /.*
/doc/rtf /doc/rtf
_unsused _unsused
CMakeLists.txt.user* CMakeLists.txt.user*

View File

@@ -97,8 +97,10 @@ public:
{ {
int code = lua_pcall (L, nargs, nresults, msgh); int code = lua_pcall (L, nargs, nresults, msgh);
if (code != LUABRIDGE_LUA_OK) if (code != LUABRIDGE_LUA_OK) {
Throw (LuaException (L, code)); // Throw (LuaException (L, code));
assert(true);
}
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@@ -128,7 +130,8 @@ protected:
private: private:
static int throwAtPanic (lua_State* L) static int throwAtPanic (lua_State* L)
{ {
throw LuaException (L, -1); // throw LuaException (L, -1);
return -1;
} }
}; };

View File

@@ -101,7 +101,8 @@ protected:
{ {
if (m_stackSize == 0) 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) 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); assert (m_stackSize > 1);
@@ -1150,7 +1152,8 @@ public:
{ {
if (m_stackSize == 1) 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) assert (lua_istable (L, -1)); // Stack: namespace table (ns)

View File

@@ -33,6 +33,7 @@
#include <cassert> #include <cassert>
#include <stdexcept> #include <stdexcept>
#include <iostream>
namespace luabridge { namespace luabridge {
@@ -320,7 +321,8 @@ public:
lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getClassKey ()); lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getClassKey ());
if (!lua_istable (L, -1)) 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); lua_setmetatable (L, -2);
return ud; return ud;
@@ -375,7 +377,8 @@ private:
lua_rawgetp (L, LUA_REGISTRYINDEX, key); lua_rawgetp (L, LUA_REGISTRYINDEX, key);
if (!lua_istable (L, -1)) 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); lua_setmetatable (L, -2);
} }

View File

@@ -360,10 +360,10 @@ endif()
if(WIN32) if(WIN32)
add_definitions(-DPSAPI_VERSION=1) add_definitions(-DPSAPI_VERSION=1)
if(${C_COMPILER} STREQUAL "cl.exe") if(${C_COMPILER} STREQUAL "cl.exe")
set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0") set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0 /EH-")
endif() endif()
else() 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) if(DEFINED ENV{QNX_HOST} OR PIP_FREERTOS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-32") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-32")
endif() endif()

View File

@@ -72,11 +72,7 @@ PIClientServer::Server::~Server() {
clean_thread->waitForFinish(); clean_thread->waitForFinish();
piDeleteSafety(clean_thread); piDeleteSafety(clean_thread);
stopServer(); stopServer();
for (auto c: clients) { closeAll();
c->aboutDelete();
c->destroy();
delete c;
}
piDeleteSafety(tcp_server); piDeleteSafety(tcp_server);
} }

View File

@@ -726,6 +726,7 @@ inline bool piDeleteSafety(T *& pointer) {
//! \~english In this example "Error!" will be printed on every \b false function return. //! \~english In this example "Error!" will be printed on every \b false function return.
//! \~russian В данном примере будет выведен "Error!" при каждом \b false возврате из функции. //! \~russian В данном примере будет выведен "Error!" при каждом \b false возврате из функции.
class PIP_EXPORT PIScopeExitCall { class PIP_EXPORT PIScopeExitCall {
NO_COPY_CLASS(PIScopeExitCall)
public: public:
//! \~\brief //! \~\brief
//! \~english Constructor that takes a function to execute //! \~english Constructor that takes a function to execute
@@ -758,8 +759,6 @@ public:
} }
private: private:
NO_COPY_CLASS(PIScopeExitCall)
std::function<void()> func; std::function<void()> func;
}; };
@@ -768,14 +767,14 @@ private:
//! \~english Inherit from this class to make your class non-trivially copyable. //! \~english Inherit from this class to make your class non-trivially copyable.
//! \~russian Наследуйтесь от этого класса чтобы сделать свой класс нетривиально копируемым. //! \~russian Наследуйтесь от этого класса чтобы сделать свой класс нетривиально копируемым.
struct PIP_EXPORT PINonTriviallyCopyable { struct PIP_EXPORT PINonTriviallyCopyable {
PINonTriviallyCopyable() noexcept = default; PINonTriviallyCopyable() = default;
PINonTriviallyCopyable(const PINonTriviallyCopyable &) noexcept = default; PINonTriviallyCopyable(const PINonTriviallyCopyable &) = default;
PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept; PINonTriviallyCopyable(PINonTriviallyCopyable &&) ;
PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) noexcept = default; PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) = default;
PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) noexcept = default; PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) = default;
~PINonTriviallyCopyable() = default; ~PINonTriviallyCopyable() = default;
}; };
inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept = default; inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) = default;
//! \~\brief //! \~\brief

View File

@@ -527,6 +527,7 @@ bool PIEthernet::listen(bool threaded) {
listen_threaded = true; listen_threaded = true;
server_bounded = false; server_bounded = false;
server_thread_.start(server_func); server_thread_.start(server_func);
server_thread_.waitForStart();
return true; return true;
} }
listen_threaded = server_bounded = false; listen_threaded = server_bounded = false;

View File

@@ -4,22 +4,22 @@
//! \~english Thread-safe variable //! \~english Thread-safe variable
//! \~russian Потокобезопасная переменная //! \~russian Потокобезопасная переменная
/* /*
PIP - Platform Independent Primitives PIP - Platform Independent Primitives
Thread-safe variable Thread-safe variable
Ivan Pelipenko peri4ko@yandex.ru Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef PIPROTECTEDVARIABLE_H #ifndef PIPROTECTEDVARIABLE_H
@@ -34,21 +34,29 @@
template<typename T> template<typename T>
class PIP_EXPORT PIProtectedVariable { class PIP_EXPORT PIProtectedVariable {
public: public:
//! \~english Constructs %PIProtectedVariable and initialize variable by value `v`.
//! \~russian Создает %PIProtectedVariable и инициализирует переменную значением `v`.
PIProtectedVariable(T v = T()): var(std::move(v)) {}
//! \~\brief //! \~\brief
//! \~english Pointer-like wrapper returned by \a getRef() while the protected value remains locked. //! \~english Pointer-like wrapper returned by \a getRef() while the protected value remains locked.
//! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным. //! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным.
class PIP_EXPORT Pointer { class PIP_EXPORT Pointer {
friend class PIProtectedVariable<T>; friend class PIProtectedVariable<T>;
NO_COPY_CLASS(Pointer);
Pointer & operator=(Pointer && other) = delete;
public: public:
//! \~english Copies wrapper state for access to the same protected value. //! \~english Move constructor - transfers ownership of the lock.
//! \~russian Копирует состояние обертки для доступа к тому же защищенному значению. //! \~russian Конструктор перемещения - передает владение блокировкой.
Pointer(const Pointer & v): pv(v.pv), counter(v.counter + 1) {} Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; };
//! \~english Destroys wrapper and releases the mutex when it owns the original lock. //! \~english Destroys wrapper and releases the mutex.
//! \~russian Уничтожает обертку и освобождает мьютекс, когда она владеет исходной блокировкой. //! \~russian Уничтожает обертку и освобождает мьютекс.
~Pointer() { ~Pointer() {
if (counter == 0) pv.mutex.unlock(); if (can_unlock) {
pv.mutex.unlock();
}
} }
//! \~english Returns pointer access to the protected value. //! \~english Returns pointer access to the protected value.
@@ -60,11 +68,11 @@ public:
T & operator*() { return pv.var; } T & operator*() { return pv.var; }
private: private:
Pointer() = delete; explicit Pointer() = delete;
Pointer(PIProtectedVariable<T> & v): pv(v) {} explicit Pointer(PIProtectedVariable<T> & v): pv(v) { pv.mutex.lock(); }
PIProtectedVariable<T> & pv; PIProtectedVariable<T> & pv;
int counter = 0; bool can_unlock = true;
}; };
//! \~english Replaces the protected value with \a v. //! \~english Replaces the protected value with \a v.
@@ -76,10 +84,7 @@ public:
//! \~english Locks the value and returns wrapper-based access to it. //! \~english Locks the value and returns wrapper-based access to it.
//! \~russian Блокирует значение и возвращает обертку для доступа к нему. //! \~russian Блокирует значение и возвращает обертку для доступа к нему.
Pointer getRef() { Pointer getRef() { return Pointer(*this); }
mutex.lock();
return Pointer(*this);
}
//! \~english Returns a copy of the protected value. //! \~english Returns a copy of the protected value.
//! \~russian Возвращает копию защищенного значения. //! \~russian Возвращает копию защищенного значения.
@@ -98,7 +103,7 @@ public:
private: private:
mutable PIMutex mutex; mutable PIMutex mutex;
T var; T var = {};
}; };

View File

@@ -105,7 +105,7 @@ extern clock_serv_t __pi_mac_clock;
//! Используйте этот метод для ожидания разниц системных времен или своего времени. //! Используйте этот метод для ожидания разниц системных времен или своего времени.
//! Если метод будет вызван для системного времени \a PISystemTime::current(), то //! Если метод будет вызван для системного времени \a PISystemTime::current(), то
//! ожидание будет почти бесконечным //! ожидание будет почти бесконечным
void PISystemTime::sleep() { void PISystemTime::sleep() const {
piUSleep(piFloord(toMicroseconds())); 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); return PISystemTime::Frequency::fromSystemTime(*this);
} }

View File

@@ -253,7 +253,7 @@ public:
//! \~english Sleep for this time //! \~english Sleep for this time
//! \~russian Ожидать это время //! \~russian Ожидать это время
void sleep(); void sleep() const;
//! \~english On *nix system assign current value to timespec struct //! \~english On *nix system assign current value to timespec struct
//! \~russian На *nix системах присваивает время к timespec структуре //! \~russian На *nix системах присваивает время к timespec структуре
@@ -261,7 +261,7 @@ public:
//! \~english Returns \a Frequency that corresponds this time interval //! \~english Returns \a Frequency that corresponds this time interval
//! \~russian Возвращает \a Frequency соответствующую этому временному интервалу //! \~russian Возвращает \a Frequency соответствующую этому временному интервалу
PISystemTime::Frequency toFrequency(); PISystemTime::Frequency toFrequency() const;
//! \~english Returns "yyyy-MM-dd hh:mm:ss.zzz" for absolute time and "<V> <d|h|m|s|ms|us|ns> ..." for relative //! \~english Returns "yyyy-MM-dd hh:mm:ss.zzz" for absolute time and "<V> <d|h|m|s|ms|us|ns> ..." for relative
//! \~russian Возвращает "yyyy-MM-dd hh:mm:ss.zzz" для абсолютного времени и "<V> <d|h|m|s|ms|us|ns> ..." для относительного //! \~russian Возвращает "yyyy-MM-dd hh:mm:ss.zzz" для абсолютного времени и "<V> <d|h|m|s|ms|us|ns> ..." для относительного

View File

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

View File

@@ -32,6 +32,7 @@ Client * createAndConnectClient() {
TEST(ClientServer, OneClient) { TEST(ClientServer, OneClient) {
auto const loop_timeout = 1000_ms; auto const loop_timeout = 1000_ms;
auto s = createServer<false, true>(); auto s = createServer<false, true>();
piMinSleep();
auto c = createAndConnectClient<TestClient<false, false>>(); auto c = createAndConnectClient<TestClient<false, false>>();
waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout);
@@ -106,6 +107,7 @@ TEST(ClientServer, ManyClients) {
constexpr int clients_count = 20; constexpr int clients_count = 20;
PIVector<ClientSendThread *> clients; PIVector<ClientSendThread *> clients;
auto s = createServer<false, false, 100_KiB>(); auto s = createServer<false, false, 100_KiB>();
piMinSleep();
piForTimes(clients_count) { piForTimes(clients_count) {
clients.append(new ClientSendThread()); clients.append(new ClientSendThread());
@@ -137,7 +139,7 @@ TEST(ClientServer, ManyClients) {
for (const auto c: clients) { for (const auto c: clients) {
c->startSend(); c->startSend();
} }
(100_ms).sleep(); loop_timeout.sleep();
EXPECT_TRUE(getClientsPings(clients) > clients_count * 2); EXPECT_TRUE(getClientsPings(clients) > clients_count * 2);
EXPECT_TRUE(getServerPongs(s) > clients_count * 2); EXPECT_TRUE(getServerPongs(s) > clients_count * 2);
piDeleteAllAndClear(clients); piDeleteAllAndClear(clients);
@@ -147,7 +149,7 @@ TEST(ClientServer, ManyClients) {
} }
TEST(ClientServer, DynamicClients) { TEST(ClientServer, DynamicClients) {
auto const loop_timeout = 100_ms; auto const loop_timeout = 10_ms;
constexpr int clients_count = 20; constexpr int clients_count = 20;
PIVector<ClientSendThread *> clients; PIVector<ClientSendThread *> clients;
PIMutex clients_mutex; PIMutex clients_mutex;
@@ -160,7 +162,6 @@ TEST(ClientServer, DynamicClients) {
clients_mutex.lock(); clients_mutex.lock();
clients << c; clients << c;
clients_mutex.unlock(); clients_mutex.unlock();
piCout << "new client" << clients.size();
}; };
piForTimes(clients_count) { piForTimes(clients_count) {
@@ -178,9 +179,8 @@ TEST(ClientServer, DynamicClients) {
piForTimes(new_cnt) { piForTimes(new_cnt) {
spawnClient(); spawnClient();
} }
piCout << "+++++++";
}, },
12_Hz); 120_Hz);
deleteThread.start( deleteThread.start(
[&clients, &clients_mutex]() { [&clients, &clients_mutex]() {
@@ -194,28 +194,20 @@ TEST(ClientServer, DynamicClients) {
clients_mutex.unlock(); clients_mutex.unlock();
if (c) { if (c) {
delete 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); EXPECT_GE(s->clientsCount(), 10);
piCout << "now clients" << clients.size();
deleteThread.stopAndWait(); deleteThread.stopAndWait();
spawnThread.stopAndWait(); spawnThread.stopAndWait();
piCout << "total clients" << clients.size();
piDeleteAllAndClear(clients); 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()); EXPECT_EQ(0, s->clientsCount());
delete s; delete s;

View File

@@ -376,7 +376,7 @@ TEST(PIMathMatrixT_Test, invert) {
matrix3 = matrix1; matrix3 = matrix1;
matrix1.invert(); matrix1.invert();
EXPECT_EQ(matrix1, matrix3); 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) { TEST(PIMathMatrixT_Test, inverted) {

View File

@@ -0,0 +1,118 @@
#include "piprotectedvariable.h"
#include "pistring.h"
#include "pithread.h"
#include "piliterals_time.h"
#include "gtest/gtest.h"
#include <atomic>
// Basic functionality tests
TEST(PIProtectedVariable_Basic, BasicFunctionality) {
// Test basic set/get with different types
PIProtectedVariable<int> pvInt;
PIProtectedVariable<double> pvDouble;
PIProtectedVariable<PIString> pvString;
pvInt.set(123);
pvDouble.set(3.14159);
pvString.set(PIString("Hello, World!"));
EXPECT_EQ(pvInt.get(), 123);
EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
EXPECT_EQ(pvString.get(), PIString("Hello, World!"));
// Test operator=
pvInt = 999;
EXPECT_EQ(pvInt.get(), 999);
// Test getRef() with Pointer
struct TestStruct {
int x = 10;
int y = 20;
int getValue() const { return x + y; }
};
PIProtectedVariable<TestStruct> pvStruct;
auto ptr = pvStruct.getRef();
EXPECT_EQ(ptr->x, 10);
EXPECT_EQ(ptr->y, 20);
EXPECT_EQ(ptr->getValue(), 30);
// Modify through pointer
*ptr = TestStruct();
ptr->x = 100;
EXPECT_EQ(pvStruct.get().x, 100);
// 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, 55);
*ptr2 = 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<int> pv;
std::atomic<int> writeCount(0);
std::atomic<int> readCount(0);
std::atomic<int> 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;
// Collect thread handles for joining
PIVector<PIThread *> threads;
// 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)++;
auto val2 = pv.getRef();
writeCount++;
ASSERT_EQ(writeCount, *val2);
}
}));
}
// 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++;
}
}
}));
}
threads.forEach([](PIThread * & t) {t->startOnce();});
threads.forEach([](PIThread * & t) {t->waitForFinish(2_s);});
piDeleteAll(threads);
// 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);
}