diff --git a/.gitignore b/.gitignore index 9a909f74..f1280e1a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /doc/rtf _unsused CMakeLists.txt.user* -/include \ No newline at end of file +/include +/release diff --git a/libs/client_server/piclientserver_client.cpp b/libs/client_server/piclientserver_client.cpp index 7c81d86b..2319584a 100644 --- a/libs/client_server/piclientserver_client.cpp +++ b/libs/client_server/piclientserver_client.cpp @@ -50,5 +50,4 @@ void PIClientServer::Client::connect(PINetworkAddress addr) { config.apply(this); tcp->connect(addr, true); tcp->startThreadedRead(); - piCout << "Connect to" << addr.toString(); } diff --git a/libs/client_server/piclientserver_server.cpp b/libs/client_server/piclientserver_server.cpp index 164f5dfa..7984d36f 100644 --- a/libs/client_server/piclientserver_server.cpp +++ b/libs/client_server/piclientserver_server.cpp @@ -123,7 +123,6 @@ void PIClientServer::Server::newClient(ServerClient * c) { config.apply(c); c->tcp->startThreadedRead(); c->connected(); - piCout << "New client"; } diff --git a/libs/main/application/pisystemmonitor.cpp b/libs/main/application/pisystemmonitor.cpp index 4454273e..522df17b 100644 --- a/libs/main/application/pisystemmonitor.cpp +++ b/libs/main/application/pisystemmonitor.cpp @@ -19,6 +19,8 @@ #include "pisystemmonitor.h" +#include + #include "pidir.h" #include "piliterals_string.h" #include "piprocess.h" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b8369eed..3eefbfcb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,3 +34,4 @@ endmacro() pip_test(math) pip_test(core) pip_test(piobject) +pip_test(client_server pip_client_server) diff --git a/tests/client_server/client_server_test.cpp b/tests/client_server/client_server_test.cpp new file mode 100644 index 00000000..59839512 --- /dev/null +++ b/tests/client_server/client_server_test.cpp @@ -0,0 +1,210 @@ +#include "pitime.h" +#include "test_client_helper.h" + +#include "gtest/gtest.h" + +template +PIClientServer::Server * createServer() { + auto s = new PIClientServer::Server(); + s->setClientFactory([] { return new TestServerClient(); }); + s->listenAll(12345); + return s; +} + +bool waitLoop(std::function exit_loop, const PISystemTime & timeout) { + PITimeMeasurer tm; + while (!exit_loop() && (tm.elapsed() < timeout)) { + piMinSleep(); + } + return tm.elapsed() < timeout; +} + +template +Client * createAndConnectClient() { + auto c = new Client(); + c->connect(PINetworkAddress::resolve("127.0.0.1", 12345)); + return c; +} + +TEST(ClientServer, OneClient) { + auto const loop_timeout = 1000_ms; + auto s = createServer(); + auto c = createAndConnectClient>(); + + waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); + EXPECT_EQ(1, s->clientsCount()); + + c->ping(); + waitLoop([c]() { return c->pongCnt() > 0; }, loop_timeout); + EXPECT_EQ(1, c->pongCnt()); + + s->forEachClient([](PIClientServer::ServerClient * sc) { static_cast *>(sc)->ping(); }); + waitLoop([c]() { return c->pongCnt() > 1; }, loop_timeout); + EXPECT_EQ(2, c->pongCnt()); + + EXPECT_TRUE(c->getTCP()->isConnected()); + + delete c; + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); + EXPECT_EQ(0, s->clientsCount()); + delete s; +} + + +class ClientSendThread { + using ClientType = TestClient; + +public: + ClientSendThread() { client = createAndConnectClient(); } + + ~ClientSendThread() { + sendThread.stopAndWait(); + delete client; + } + + void startSend() { + auto c = client; + sendThread.start([c] { c->ping(); }, 100._Hz); + } + + void sendOnce() {client->ping();} + + ClientType * client = nullptr; + PIThread sendThread; +}; + + +int getServerPongs(PIClientServer::Server * s) { + int pongs = 0; + s->forEachClient([&pongs](PIClientServer::ServerClient * sc){ + const auto c = static_cast *>(sc); + pongs += c->pongCnt(); + }); + return pongs; +} + +int getClientsPongs(const PIVector & clients) { + int pongs = 0; + clients.forEach([&pongs](ClientSendThread * c){ + pongs += c->client->pongCnt(); + }); + return pongs; +} + +int getClientsPings(const PIVector & clients) { + int pings = 0; + clients.forEach([&pings](ClientSendThread * c){ + pings += c->client->pingCnt(); + }); + return pings; +} + + +TEST(ClientServer, ManyClients) { + auto const loop_timeout = 1000_ms; + constexpr int clients_count = 20; + PIVector clients; + auto s = createServer(); + + piForTimes(clients_count) { + clients.append(new ClientSendThread()); + } + EXPECT_EQ(clients_count, clients.size_s()); + + waitLoop([s]() { return s->clientsCount() == clients_count; }, loop_timeout); + EXPECT_EQ(clients_count, s->clientsCount()); + + EXPECT_EQ(0, getServerPongs(s)); + + EXPECT_EQ(getClientsPings(clients), 0); + + for (const auto c : clients) { + c->sendOnce(); + } + + EXPECT_EQ(getClientsPings(clients), clients_count); + EXPECT_TRUE(clients.every([](ClientSendThread * c){return c->client->pingCnt() == 1;})); + EXPECT_TRUE(clients.every([](ClientSendThread * c){return c->client->pongCnt() == 0;})); + waitLoop([s]() { return getServerPongs(s) >= clients_count; }, loop_timeout); + EXPECT_EQ(clients_count, getServerPongs(s)); + + s->forEachClient([](PIClientServer::ServerClient * sc) { static_cast *>(sc)->ping(); }); + const auto clientCheckPong = [&clients](){return clients.every([](ClientSendThread * c){return c->client->pongCnt() == 1;});}; + waitLoop([&clientCheckPong]() { return clientCheckPong(); }, loop_timeout); + EXPECT_TRUE(clientCheckPong()); + + for (const auto c : clients) { + c->startSend(); + } + (100_ms).sleep(); + EXPECT_TRUE(getClientsPings(clients) > clients_count*2); + EXPECT_TRUE(getServerPongs(s) > clients_count*2); + piDeleteAllAndClear(clients); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); + EXPECT_EQ(0, s->clientsCount()); + delete s; +} + +TEST(ClientServer, DynamicClients) { + auto const loop_timeout = 1000_ms; + constexpr int clients_count = 20; + PIVector clients; + PIMutex clients_mutex; + auto s = createServer(); + + const auto spawnClient = [&clients, &clients_mutex]() { + auto c = new ClientSendThread(); + c->startSend(); + clients_mutex.lock(); + clients << c; + clients_mutex.unlock(); + piCout << "new client" << clients.size(); + }; + + piForTimes(clients_count) { + spawnClient(); + } + + PIThread spawnThread; + PIThread deleteThread; + + spawnThread.start([&spawnClient](){ + const int new_cnt = randomi() % 10; + piForTimes(new_cnt) { + spawnClient(); + } + }, 12_Hz); + + deleteThread.start([&clients, &clients_mutex](){ + const int rm_cnt = randomi() % 10; + piForTimes(rm_cnt) { + ClientSendThread * c = nullptr; + clients_mutex.lock(); + if (clients.size() > 10) { + c = clients.take_back(); + } + clients_mutex.unlock(); + if (c) { + delete c; + piCout << "remove client" << clients.size(); + } + } + }, 13_Hz); + + (1_s).sleep(); + + EXPECT_GE(s->clientsCount(), 10); + + piCout << "now clients" << clients.size(); + + //delete s; + + deleteThread.stopAndWait(); + spawnThread.stopAndWait(); + + piCout << "total clients" << clients.size(); + + piDeleteAllAndClear(clients); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); + EXPECT_EQ(0, s->clientsCount()); +} diff --git a/tests/client_server/test_client_helper.h b/tests/client_server/test_client_helper.h new file mode 100644 index 00000000..2c261738 --- /dev/null +++ b/tests/client_server/test_client_helper.h @@ -0,0 +1,77 @@ +#ifndef TEST_CLIENT_HELPER_H +#define TEST_CLIENT_HELPER_H + +#include "piclientserver_client.h" +#include "piclientserver_server.h" +#include "piethernet.h" +#include "piliterals.h" +#include "pithread.h" + + +template +class TestClientBase { +public: + int pongCnt() const { return pong_cnt; } + + int pingCnt() const { return ping_cnt; } + + void ping() { + const auto data = PIByteArray(WriteSize); + writeInternal(data); + ping_cnt++; + } + +protected: + virtual void writeInternal(const PIByteArray & ba) = 0; + + void readInternal(const PIByteArray & ba) { + last_read_size = ba.size(); + pong_cnt++; + if (WithPong) ping(); + } + +private: + int pong_cnt = 0; + int ping_cnt = 0; + ullong last_read_size = 0; +}; + + +template +class TestServerClient + : public PIClientServer::ServerClient + , public TestClientBase { + using Base = TestClientBase; + +private: + void readed(PIByteArray data) override { Base::readInternal(data); } + + void connected() override { + if (WithConnectPing) { + Base::ping(); + } + } + + void writeInternal(const PIByteArray & ba) override { write(ba); } +}; + + +template +class TestClient + : public PIClientServer::Client + , public TestClientBase { + using Base = TestClientBase; + +private: + void readed(PIByteArray data) override { Base::readInternal(data); } + + void connected() override { + if (WithConnectPing) { + Base::ping(); + } + } + + void writeInternal(const PIByteArray & ba) override { write(ba); } +}; + +#endif // TEST_CLIENT_HELPER_H diff --git a/tests/piobject/connect.cpp b/tests/piobject/connect.cpp index 1147597a..a5602cd8 100644 --- a/tests/piobject/connect.cpp +++ b/tests/piobject/connect.cpp @@ -31,23 +31,23 @@ private: TEST(PiobjectConnections, CONNECT0) { Object a; Object b; - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); CONNECT0(void, &a, event_test, &b, handler_test); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); // piCout << "================"; } @@ -57,105 +57,105 @@ TEST(PiobjectConnections, CONNECT0_DISCONNECT) { Object b; a.setName("A"); b.setName("B"); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); CONNECT0(void, &a, event_test, &b, handler_test); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); PIObject::piDisconnect(&a, "event_test"); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); // piCout << "================"; } TEST(PiobjectConnections, CONNECTU) { Object a; Object b; - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); CONNECTU(&a, event_test, &b, handler_test); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); // piCout << "================"; } TEST(PiobjectConnections, CONNECTU_DISCONNECT) { Object a; Object b; - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); CONNECTU(&a, event_test, &b, handler_test); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 0); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 0); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); PIObject::piDisconnect(&a, "event_test"); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); a.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); b.test(); - ASSERT_EQ(a.getX(), 0); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 0); + EXPECT_EQ(b.getX(), 1); a.handler_val(99); - ASSERT_EQ(a.getX(), 99); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 99); + EXPECT_EQ(b.getX(), 1); CONNECTU(&a, event_val, &b, handler_val); - ASSERT_EQ(a.getX(), 99); - ASSERT_EQ(b.getX(), 1); + EXPECT_EQ(a.getX(), 99); + EXPECT_EQ(b.getX(), 1); a.test_val(); - ASSERT_EQ(a.getX(), 99); - ASSERT_EQ(b.getX(), 99); + EXPECT_EQ(a.getX(), 99); + EXPECT_EQ(b.getX(), 99); a.handler_val(-1); - ASSERT_EQ(a.getX(), -1); - ASSERT_EQ(b.getX(), 99); + EXPECT_EQ(a.getX(), -1); + EXPECT_EQ(b.getX(), 99); PIObject::piDisconnect(&a, "event_val", &b); - ASSERT_EQ(a.getX(), -1); - ASSERT_EQ(b.getX(), 99); + EXPECT_EQ(a.getX(), -1); + EXPECT_EQ(b.getX(), 99); a.test_val(); - ASSERT_EQ(a.getX(), -1); - ASSERT_EQ(b.getX(), 99); + EXPECT_EQ(a.getX(), -1); + EXPECT_EQ(b.getX(), 99); // piCout << "================"; }