Skip to content

Commit

Permalink
[Net] Fix TCP/UDP server network tests
Browse files Browse the repository at this point in the history
Some tests have been removed since there's no way to guarantee they will
pass.

Other tests have been refactored to ensure proper waiting, and taking
into account potential out-of-order delivery (which is unlikely in test
scenarios but expecting a specific order on a UDP socket is wrong and
OSes makes no promises of ordered delivery on localhost).
  • Loading branch information
Faless committed Dec 6, 2024
1 parent aa8d9b8 commit 675cdfe
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 159 deletions.
93 changes: 61 additions & 32 deletions tests/core/io/test_tcp_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,22 @@
#include "core/io/tcp_server.h"
#include "tests/test_macros.h"

#include <functional>

namespace TestTCPServer {

const int PORT = 12345;
const IPAddress LOCALHOST("127.0.0.1");
const uint32_t SLEEP_DURATION = 1000;
const uint64_t MAX_WAIT_USEC = 100000;

void wait_for_condition(std::function<bool()> f_test) {
const uint64_t time = OS::get_singleton()->get_ticks_usec();
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
}
}

Ref<TCPServer> create_server(const IPAddress &p_address, int p_port) {
Ref<TCPServer> server;
server.instantiate();
Expand All @@ -66,11 +75,9 @@ Ref<StreamPeerTCP> create_client(const IPAddress &p_address, int p_port) {
}

Ref<StreamPeerTCP> accept_connection(Ref<TCPServer> &p_server) {
// Required to get the connection properly established.
const uint64_t time = OS::get_singleton()->get_ticks_usec();
while (!p_server->is_connection_available() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
}
wait_for_condition([&]() {
return p_server->is_connection_available();
});

REQUIRE(p_server->is_connection_available());
Ref<StreamPeerTCP> client_from_server = p_server->take_connection();
Expand All @@ -81,16 +88,6 @@ Ref<StreamPeerTCP> accept_connection(Ref<TCPServer> &p_server) {
return client_from_server;
}

Error poll(Ref<StreamPeerTCP> p_client) {
const uint64_t time = OS::get_singleton()->get_ticks_usec();
Error err = p_client->poll();
while (err != Error::OK && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
err = p_client->poll();
}
return err;
}

TEST_CASE("[TCPServer] Instantiation") {
Ref<TCPServer> server;
server.instantiate();
Expand All @@ -104,7 +101,10 @@ TEST_CASE("[TCPServer] Accept a connection and receive/send data") {
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);

REQUIRE_EQ(poll(client), Error::OK);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});

CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);

// Sending data from client to server.
Expand Down Expand Up @@ -135,9 +135,25 @@ TEST_CASE("[TCPServer] Handle multiple clients at the same time") {
clients_from_server.push_back(accept_connection(server));
}

// Calling poll() to update client status.
wait_for_condition([&]() {
bool exit = true;
for (Ref<StreamPeerTCP> &c : clients) {
if (c->poll() != Error::OK) {
return true;
}
StreamPeerTCP::Status status = c->get_status();
if (status != StreamPeerTCP::STATUS_CONNECTED && status != StreamPeerTCP::STATUS_CONNECTING) {
return true;
}
if (status != StreamPeerTCP::STATUS_CONNECTED) {
exit = false;
}
}
return exit;
});

for (Ref<StreamPeerTCP> &c : clients) {
REQUIRE_EQ(poll(c), Error::OK);
REQUIRE_EQ(c->get_status(), StreamPeerTCP::STATUS_CONNECTED);
}

// Sending data from each client to server.
Expand All @@ -158,7 +174,10 @@ TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") {
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);

REQUIRE_EQ(poll(client), Error::OK);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});

CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);

// Sending data from client to server.
Expand All @@ -180,25 +199,24 @@ TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") {

CHECK_FALSE(server->is_connection_available());

time = OS::get_singleton()->get_ticks_usec();
Error err = new_client->poll();
while (err != Error::OK && err != Error::ERR_CONNECTION_ERROR && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
err = new_client->poll();
}
REQUIRE((err == Error::OK || err == Error::ERR_CONNECTION_ERROR));
StreamPeerTCP::Status status = new_client->get_status();
CHECK((status == StreamPeerTCP::STATUS_CONNECTING || status == StreamPeerTCP::STATUS_ERROR));
wait_for_condition([&]() {
return new_client->poll() != Error::OK || new_client->get_status() == StreamPeerTCP::STATUS_ERROR;
});

CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_ERROR);
new_client->disconnect_from_host();
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_NONE);
}

TEST_CASE("[TCPServer] Should disconnect client") {
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);

REQUIRE_EQ(poll(client), Error::OK);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});

CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);

// Sending data from client to server.
Expand All @@ -210,12 +228,23 @@ TEST_CASE("[TCPServer] Should disconnect client") {
server->stop();
CHECK_FALSE(server->is_listening());

// Reading for a closed connection will print an error.
// Wait for disconnection
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_NONE;
});

// Wait for disconnection
wait_for_condition([&]() {
return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerTCP::STATUS_NONE;
});

CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE);
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_NONE);

ERR_PRINT_OFF;
CHECK_EQ(client->get_string(), String());
CHECK_EQ(client_from_server->get_string(), String());
ERR_PRINT_ON;
REQUIRE_EQ(poll(client), Error::OK);
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE);
}

} // namespace TestTCPServer
Expand Down
Loading

0 comments on commit 675cdfe

Please sign in to comment.