mirror of
https://github.com/kierankihn/uno-game.git
synced 2025-12-27 02:13:18 +08:00
test(network): add unit tests for NetworkClient and NetworkServer
- Introduced comprehensive test cases for `NetworkClient` and `NetworkServer` functionality. - Added tests for constructor, `connect`, `send`, `read`, and round-trip scenarios in `NetworkClientTest`. - Added tests for player management, message sending, and concurrent access in `NetworkServerTest`. - Updated `CMakeLists.txt` to include `NetworkClientTest` and `NetworkServerTest` in the build configuration.
This commit is contained in:
@@ -11,6 +11,8 @@ add_executable(uno-game-test
|
||||
unit/game/PlayerTest.cpp
|
||||
unit/game/GameStateTest.cpp
|
||||
unit/network/MessageSerializerTest.cpp
|
||||
unit/network/NetworkServerTest.cpp
|
||||
unit/network/NetworkClientTest.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(uno-game-test
|
||||
|
||||
557
test/unit/network/NetworkClientTest.cpp
Normal file
557
test/unit/network/NetworkClientTest.cpp
Normal file
@@ -0,0 +1,557 @@
|
||||
/**
|
||||
* @file NetworkClientTest.cpp
|
||||
*
|
||||
* @author Yuzhe Guo
|
||||
* @date 2025.11.28
|
||||
*/
|
||||
|
||||
#include "../../../src/network/NetworkClient.h"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread>
|
||||
|
||||
using namespace UNO::NETWORK;
|
||||
|
||||
// Helper class to create a simple echo server for testing
|
||||
class SimpleTestServer {
|
||||
private:
|
||||
asio::io_context io_context_;
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
std::thread server_thread_;
|
||||
std::atomic<bool> running_{true};
|
||||
|
||||
public:
|
||||
SimpleTestServer(uint16_t port) : acceptor_(io_context_, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port))
|
||||
{
|
||||
server_thread_ = std::thread([this]() { runServer(); });
|
||||
}
|
||||
|
||||
~SimpleTestServer()
|
||||
{
|
||||
running_ = false;
|
||||
asio::error_code ec;
|
||||
acceptor_.close(ec);
|
||||
io_context_.stop();
|
||||
if (server_thread_.joinable()) {
|
||||
server_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void runServer()
|
||||
{
|
||||
while (running_) {
|
||||
try {
|
||||
asio::ip::tcp::socket socket(io_context_);
|
||||
acceptor_.accept(socket);
|
||||
|
||||
// Echo server: read message and send it back
|
||||
while (socket.is_open() && running_) {
|
||||
try {
|
||||
size_t length;
|
||||
// Use async read with timeout simulation via non-blocking approach
|
||||
asio::error_code ec;
|
||||
|
||||
// Try to read the length with error handling
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)), ec);
|
||||
if (ec) {
|
||||
break; // Connection closed or error
|
||||
}
|
||||
|
||||
if (length <= 10 * 1024 * 1024) {
|
||||
std::vector<char> buffer(length);
|
||||
asio::read(socket, asio::buffer(buffer), ec);
|
||||
if (ec) {
|
||||
break; // Connection closed or error
|
||||
}
|
||||
|
||||
// Echo back
|
||||
asio::write(socket, asio::buffer(&length, sizeof(length)), ec);
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
asio::write(socket, asio::buffer(buffer), ec);
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure socket is closed when exiting the loop
|
||||
asio::error_code ec;
|
||||
socket.close(ec);
|
||||
}
|
||||
catch (...) {
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ========== NetworkClient Constructor Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, ConstructorWithCallback)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
|
||||
EXPECT_NO_THROW({ NetworkClient client(callback); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ConstructorWithEmptyCallback)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
|
||||
EXPECT_NO_THROW({ NetworkClient client(callback); });
|
||||
}
|
||||
|
||||
// ========== NetworkClient Connect Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, ConnectToValidServer)
|
||||
{
|
||||
SimpleTestServer server(30001);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
EXPECT_NO_THROW({ client.connect("127.0.0.1", 30001); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ConnectToLocalhost)
|
||||
{
|
||||
SimpleTestServer server(30002);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
EXPECT_NO_THROW({ client.connect("localhost", 30002); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ConnectToInvalidHost)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
// Should throw when trying to resolve invalid host
|
||||
EXPECT_THROW({ client.connect("invalid.host.that.does.not.exist.12345", 30003); }, std::exception);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ConnectToUnreachablePort)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
// Should throw when trying to connect to a port with no server
|
||||
EXPECT_THROW({ client.connect("127.0.0.1", 30004); }, std::exception);
|
||||
}
|
||||
|
||||
// ========== NetworkClient Send Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, SendSimpleMessage)
|
||||
{
|
||||
SimpleTestServer server(30005);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30005);
|
||||
|
||||
EXPECT_NO_THROW({ client.send("Hello, Server!"); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendEmptyMessage)
|
||||
{
|
||||
SimpleTestServer server(30006);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30006);
|
||||
|
||||
EXPECT_NO_THROW({ client.send(""); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendLargeMessage)
|
||||
{
|
||||
SimpleTestServer server(30007);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30007);
|
||||
|
||||
std::string large_message(1024 * 1024, 'X'); // 1MB message
|
||||
EXPECT_NO_THROW({ client.send(large_message); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendMessageWithSpecialCharacters)
|
||||
{
|
||||
SimpleTestServer server(30008);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30008);
|
||||
|
||||
EXPECT_NO_THROW({ client.send("Special: \n\t\r\"'\\"); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendMessageWithUnicode)
|
||||
{
|
||||
SimpleTestServer server(30009);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30009);
|
||||
|
||||
EXPECT_NO_THROW({ client.send("Unicode: 你好世界 🎮🃏"); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendMultipleMessages)
|
||||
{
|
||||
SimpleTestServer server(30010);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30010);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_NO_THROW({ client.send("Message " + std::to_string(i)); });
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendJSONMessage)
|
||||
{
|
||||
SimpleTestServer server(30011);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30011);
|
||||
|
||||
std::string json_message = R"({"status":"OK","type":"TEST","data":"value"})";
|
||||
EXPECT_NO_THROW({ client.send(json_message); });
|
||||
}
|
||||
|
||||
// ========== NetworkClient Read Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, ReadMessageFromServer)
|
||||
{
|
||||
SimpleTestServer server(30012);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30012);
|
||||
|
||||
std::string test_message = "Test Message";
|
||||
client.send(test_message);
|
||||
|
||||
std::string received;
|
||||
EXPECT_NO_THROW({ received = client.read(); });
|
||||
|
||||
EXPECT_EQ(received, test_message);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadEmptyMessage)
|
||||
{
|
||||
SimpleTestServer server(30013);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30013);
|
||||
|
||||
client.send("");
|
||||
|
||||
std::string received;
|
||||
EXPECT_NO_THROW({ received = client.read(); });
|
||||
|
||||
EXPECT_EQ(received, "");
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadLargeMessage)
|
||||
{
|
||||
SimpleTestServer server(30014);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30014);
|
||||
|
||||
std::string large_message(100 * 1024, 'L'); // 100KB
|
||||
client.send(large_message);
|
||||
|
||||
std::string received;
|
||||
EXPECT_NO_THROW({ received = client.read(); });
|
||||
|
||||
EXPECT_EQ(received, large_message);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadMessageWithSpecialCharacters)
|
||||
{
|
||||
SimpleTestServer server(30015);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30015);
|
||||
|
||||
std::string special_message = "Special: \n\t\r\"'\\";
|
||||
client.send(special_message);
|
||||
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(received, special_message);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadMessageWithUnicode)
|
||||
{
|
||||
SimpleTestServer server(30016);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30016);
|
||||
|
||||
std::string unicode_message = "Unicode: 你好世界 🎮";
|
||||
client.send(unicode_message);
|
||||
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(received, unicode_message);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadMultipleMessages)
|
||||
{
|
||||
SimpleTestServer server(30017);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30017);
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
std::string test_message = "Message " + std::to_string(i);
|
||||
client.send(test_message);
|
||||
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(received, test_message);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Round-trip Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, RoundTripSimpleMessage)
|
||||
{
|
||||
SimpleTestServer server(30018);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30018);
|
||||
|
||||
std::string original = "Round trip test";
|
||||
client.send(original);
|
||||
std::string received = client.read();
|
||||
|
||||
EXPECT_EQ(original, received);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, RoundTripJSONMessage)
|
||||
{
|
||||
SimpleTestServer server(30019);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30019);
|
||||
|
||||
std::string json = R"({"player":"Alice","action":"DRAW_CARD","count":3})";
|
||||
client.send(json);
|
||||
std::string received = client.read();
|
||||
|
||||
EXPECT_EQ(json, received);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, RoundTripMultipleMessages)
|
||||
{
|
||||
SimpleTestServer server(30020);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30020);
|
||||
|
||||
std::vector<std::string> messages = {"First message", "Second message with 123", "Third: special chars \n\t", "Fourth: 你好", ""};
|
||||
|
||||
for (const auto &msg : messages) {
|
||||
client.send(msg);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(msg, received);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, RoundTripLargeMessages)
|
||||
{
|
||||
SimpleTestServer server(30021);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30021);
|
||||
|
||||
for (size_t size : {1024, 10240, 102400}) {
|
||||
std::string large_msg(size, 'Z');
|
||||
client.send(large_msg);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(large_msg.size(), received.size());
|
||||
EXPECT_EQ(large_msg, received);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Edge Cases ==========
|
||||
|
||||
TEST(NetworkClientTest, SendBeforeConnect)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
// Should throw because not connected
|
||||
EXPECT_THROW({ client.send("Message before connect"); }, std::exception);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, ReadBeforeConnect)
|
||||
{
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
// Should throw because not connected
|
||||
EXPECT_THROW({ client.read(); }, std::exception);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, MultipleConnectAttempts)
|
||||
{
|
||||
SimpleTestServer server1(30022);
|
||||
SimpleTestServer server2(30023);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
|
||||
// First connection should succeed
|
||||
EXPECT_NO_THROW({ client.connect("127.0.0.1", 30022); });
|
||||
|
||||
// Second connection attempt should also work (replaces first connection)
|
||||
EXPECT_NO_THROW({ client.connect("127.0.0.1", 30023); });
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, RapidSendAndRead)
|
||||
{
|
||||
SimpleTestServer server(30025);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30025);
|
||||
|
||||
// Rapidly send and read 100 messages
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::string msg = std::to_string(i);
|
||||
client.send(msg);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(msg, received);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, VeryLongMessageContent)
|
||||
{
|
||||
SimpleTestServer server(30026);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30026);
|
||||
|
||||
// Create a complex message with various characters
|
||||
std::string complex_message;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
complex_message += "Line " + std::to_string(i) + ": Hello World! 你好世界 🎮\n";
|
||||
}
|
||||
|
||||
client.send(complex_message);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(complex_message, received);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, SendBinaryData)
|
||||
{
|
||||
SimpleTestServer server(30027);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30027);
|
||||
|
||||
// Create binary data with all byte values
|
||||
std::string binary_data;
|
||||
for (int i = 1; i < 256; ++i) { // Skip 0 to avoid null terminator issues
|
||||
binary_data += static_cast<char>(i);
|
||||
}
|
||||
|
||||
client.send(binary_data);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(binary_data.size(), received.size());
|
||||
EXPECT_EQ(binary_data, received);
|
||||
}
|
||||
|
||||
TEST(NetworkClientTest, AlternatingReadAndWrite)
|
||||
{
|
||||
SimpleTestServer server(30028);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30028);
|
||||
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if (i % 2 == 0) {
|
||||
client.send("Message " + std::to_string(i));
|
||||
}
|
||||
else {
|
||||
std::string received = client.read();
|
||||
EXPECT_FALSE(received.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Protocol Compliance Tests ==========
|
||||
|
||||
TEST(NetworkClientTest, MessageLengthPrefixCorrect)
|
||||
{
|
||||
SimpleTestServer server(30029);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto callback = [](std::string message) {};
|
||||
NetworkClient client(callback);
|
||||
client.connect("127.0.0.1", 30029);
|
||||
|
||||
// Test various message lengths
|
||||
for (size_t len : {0, 1, 10, 100, 1000, 10000}) {
|
||||
std::string msg(len, 'A');
|
||||
client.send(msg);
|
||||
std::string received = client.read();
|
||||
EXPECT_EQ(len, received.size());
|
||||
EXPECT_EQ(msg, received);
|
||||
}
|
||||
}
|
||||
636
test/unit/network/NetworkServerTest.cpp
Normal file
636
test/unit/network/NetworkServerTest.cpp
Normal file
@@ -0,0 +1,636 @@
|
||||
/**
|
||||
* @file NetworkServerTest.cpp
|
||||
*
|
||||
* @author Yuzhe Guo
|
||||
* @date 2025.11.28
|
||||
*/
|
||||
|
||||
#include "../../../src/network/NetworkServer.h"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
using namespace UNO::NETWORK;
|
||||
|
||||
// ========== NetworkServer Constructor Tests ==========
|
||||
|
||||
TEST(NetworkServerTest, ConstructorWithValidPort)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
EXPECT_NO_THROW({ NetworkServer server(20001, callback); });
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, ConstructorWithZeroPort)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
// Port 0 should let OS assign a port
|
||||
EXPECT_NO_THROW({ NetworkServer server(0, callback); });
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, ConstructorWithHighPort)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
EXPECT_NO_THROW({ NetworkServer server(65535, callback); });
|
||||
}
|
||||
|
||||
// ========== NetworkServer AddPlayer Tests ==========
|
||||
|
||||
TEST(NetworkServerTest, AddPlayerSinglePlayer)
|
||||
{
|
||||
std::atomic<bool> callback_called{false};
|
||||
std::atomic<size_t> received_player_id{999};
|
||||
std::string received_message;
|
||||
std::mutex msg_mutex;
|
||||
|
||||
auto callback = [&callback_called, &received_player_id, &received_message, &msg_mutex](size_t player_id, std::string message) {
|
||||
callback_called = true;
|
||||
received_player_id = player_id;
|
||||
std::lock_guard<std::mutex> lock(msg_mutex);
|
||||
received_message = std::move(message);
|
||||
};
|
||||
|
||||
NetworkServer server(20002, callback);
|
||||
|
||||
// Start server in background thread
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
// Give server time to start
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Connect a client
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20002");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
// Give time for connection to be established
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Send a message to trigger callback
|
||||
std::string test_message = "Hello Server";
|
||||
size_t length = test_message.size();
|
||||
asio::write(socket, asio::buffer(&length, sizeof(length)));
|
||||
asio::write(socket, asio::buffer(test_message));
|
||||
|
||||
// Give time for message to be received
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
// Verify callback was called
|
||||
EXPECT_TRUE(callback_called);
|
||||
EXPECT_EQ(received_player_id, 0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(msg_mutex);
|
||||
EXPECT_EQ(received_message, "Hello Server");
|
||||
}
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, AddPlayerMultiplePlayers)
|
||||
{
|
||||
std::mutex mutex;
|
||||
size_t message_count{0};
|
||||
std::set<size_t> received_player_ids;
|
||||
|
||||
auto callback = [&message_count, &received_player_ids, &mutex](size_t player_id, std::string message) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
message_count++;
|
||||
received_player_ids.insert(player_id);
|
||||
};
|
||||
|
||||
NetworkServer server(20003, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Connect multiple clients
|
||||
std::vector<std::unique_ptr<asio::io_context>> contexts;
|
||||
std::vector<std::unique_ptr<asio::ip::tcp::socket>> sockets;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
contexts.push_back(std::make_unique<asio::io_context>());
|
||||
sockets.push_back(std::make_unique<asio::ip::tcp::socket>(*contexts.back()));
|
||||
|
||||
asio::ip::tcp::resolver resolver(*contexts.back());
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20003");
|
||||
asio::connect(*sockets.back(), endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Each client sends a message
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
std::string msg = "Message from client " + std::to_string(i);
|
||||
size_t length = msg.size();
|
||||
asio::write(*sockets[i], asio::buffer(&length, sizeof(length)));
|
||||
asio::write(*sockets[i], asio::buffer(msg));
|
||||
}
|
||||
|
||||
// Give time for messages to be received
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
|
||||
// Verify all callbacks were called
|
||||
EXPECT_EQ(message_count, 3);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
EXPECT_EQ(received_player_ids.size(), 3);
|
||||
EXPECT_TRUE(received_player_ids.contains(0));
|
||||
EXPECT_TRUE(received_player_ids.contains(1));
|
||||
EXPECT_TRUE(received_player_ids.contains(2));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for (auto &socket : sockets) {
|
||||
socket->close();
|
||||
}
|
||||
for (auto &context : contexts) {
|
||||
context->stop();
|
||||
}
|
||||
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== NetworkServer Send Tests ==========
|
||||
|
||||
TEST(NetworkServerTest, SendMessageToValidPlayer)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20004, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Connect a client
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20004");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Send message to player 0
|
||||
EXPECT_NO_THROW({ server.send(0, "test message"); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Read the message from client side to verify it was sent
|
||||
std::atomic<bool> message_received{false};
|
||||
std::string received_msg;
|
||||
std::thread read_thread([&socket, &message_received, &received_msg]() {
|
||||
try {
|
||||
size_t length;
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)));
|
||||
std::vector<char> buffer(length);
|
||||
asio::read(socket, asio::buffer(buffer));
|
||||
received_msg = std::string(buffer.begin(), buffer.end());
|
||||
if (received_msg == "test message") {
|
||||
message_received = true;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
});
|
||||
|
||||
read_thread.join();
|
||||
EXPECT_TRUE(message_received);
|
||||
EXPECT_EQ(received_msg, "test message");
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendMessageToInvalidPlayer)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20005, callback);
|
||||
|
||||
// Try to send to non-existent player
|
||||
EXPECT_THROW({ server.send(999, "test message"); }, std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendEmptyMessage)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20006, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20006");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
EXPECT_NO_THROW({ server.send(0, ""); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Verify empty message was received
|
||||
std::atomic<bool> message_received{false};
|
||||
std::thread read_thread([&socket, &message_received]() {
|
||||
try {
|
||||
size_t length;
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)));
|
||||
if (length == 0) {
|
||||
message_received = true;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
});
|
||||
|
||||
read_thread.join();
|
||||
EXPECT_TRUE(message_received);
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendLargeMessage)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20007, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20007");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::string large_message(1024 * 1024, 'A'); // 1MB message
|
||||
EXPECT_NO_THROW({ server.send(0, large_message); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
// Verify large message was received
|
||||
std::atomic<bool> message_received{false};
|
||||
std::thread read_thread([&socket, &message_received, &large_message]() {
|
||||
try {
|
||||
size_t length;
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)));
|
||||
std::vector<char> buffer(length);
|
||||
asio::read(socket, asio::buffer(buffer));
|
||||
std::string received(buffer.begin(), buffer.end());
|
||||
if (received == large_message) {
|
||||
message_received = true;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
});
|
||||
|
||||
read_thread.join();
|
||||
EXPECT_TRUE(message_received);
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendMessageWithSpecialCharacters)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20008, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20008");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::string special_msg = "Special chars: \n\t\r\"'\\";
|
||||
EXPECT_NO_THROW({ server.send(0, special_msg); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Verify message with special characters was received
|
||||
std::atomic<bool> message_received{false};
|
||||
std::thread read_thread([&socket, &message_received, &special_msg]() {
|
||||
try {
|
||||
size_t length;
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)));
|
||||
std::vector<char> buffer(length);
|
||||
asio::read(socket, asio::buffer(buffer));
|
||||
std::string received(buffer.begin(), buffer.end());
|
||||
if (received == special_msg) {
|
||||
message_received = true;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
});
|
||||
|
||||
read_thread.join();
|
||||
EXPECT_TRUE(message_received);
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendMessageWithUnicode)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20009, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20009");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::string unicode_msg = "Unicode: 你好世界 🎮";
|
||||
EXPECT_NO_THROW({ server.send(0, unicode_msg); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Verify Unicode message was received
|
||||
std::atomic<bool> message_received{false};
|
||||
std::thread read_thread([&socket, &message_received, &unicode_msg]() {
|
||||
try {
|
||||
size_t length;
|
||||
asio::read(socket, asio::buffer(&length, sizeof(length)));
|
||||
std::vector<char> buffer(length);
|
||||
asio::read(socket, asio::buffer(buffer));
|
||||
std::string received(buffer.begin(), buffer.end());
|
||||
if (received == unicode_msg) {
|
||||
message_received = true;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
});
|
||||
|
||||
read_thread.join();
|
||||
EXPECT_TRUE(message_received);
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Session Tests ==========
|
||||
|
||||
TEST(SessionTest, SessionCreation)
|
||||
{
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
|
||||
EXPECT_NO_THROW({ Session session(0, std::move(socket)); });
|
||||
}
|
||||
|
||||
TEST(SessionTest, SessionStartWithCallback)
|
||||
{
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
|
||||
auto session = std::make_shared<Session>(0, std::move(socket));
|
||||
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
EXPECT_NO_THROW({ session->start(callback); });
|
||||
}
|
||||
|
||||
// ========== Concurrent Access Tests ==========
|
||||
|
||||
TEST(NetworkServerTest, ConcurrentSendToSamePlayer)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20010, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20010");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Multiple threads sending to same player
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
threads.emplace_back([&server, i]() { server.send(0, "Message " + std::to_string(i)); });
|
||||
}
|
||||
|
||||
for (auto &t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, ConcurrentSendToDifferentPlayers)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20011, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Connect multiple clients
|
||||
std::vector<std::unique_ptr<asio::io_context>> contexts;
|
||||
std::vector<std::unique_ptr<asio::ip::tcp::socket>> sockets;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
contexts.push_back(std::make_unique<asio::io_context>());
|
||||
sockets.push_back(std::make_unique<asio::ip::tcp::socket>(*contexts.back()));
|
||||
|
||||
asio::ip::tcp::resolver resolver(*contexts.back());
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20011");
|
||||
asio::connect(*sockets.back(), endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Multiple threads sending to different players
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
threads.emplace_back([&server, i]() { server.send(i, "Message to player " + std::to_string(i)); });
|
||||
}
|
||||
|
||||
for (auto &t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Cleanup
|
||||
for (auto &socket : sockets) {
|
||||
socket->close();
|
||||
}
|
||||
for (auto &context : contexts) {
|
||||
context->stop();
|
||||
}
|
||||
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Edge Cases ==========
|
||||
|
||||
TEST(NetworkServerTest, MultipleMessagesToSamePlayer)
|
||||
{
|
||||
std::atomic<size_t> callback_count{0};
|
||||
auto callback = [&callback_count](size_t player_id, std::string message) { callback_count++; };
|
||||
|
||||
NetworkServer server(20012, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20012");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Send multiple messages from server
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_NO_THROW({ server.send(0, "Message " + std::to_string(i)); });
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Client sends messages back to trigger callbacks
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
std::string msg = "Client message " + std::to_string(i);
|
||||
size_t length = msg.size();
|
||||
asio::write(socket, asio::buffer(&length, sizeof(length)));
|
||||
asio::write(socket, asio::buffer(msg));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
// Verify callbacks were called
|
||||
EXPECT_EQ(callback_count, 5);
|
||||
|
||||
socket.close();
|
||||
io_context.stop();
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NetworkServerTest, SendAfterPlayerDisconnect)
|
||||
{
|
||||
auto callback = [](size_t player_id, std::string message) {};
|
||||
|
||||
NetworkServer server(20013, callback);
|
||||
|
||||
std::thread server_thread([&server]() { server.run(); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
{
|
||||
asio::io_context io_context;
|
||||
asio::ip::tcp::socket socket(io_context);
|
||||
asio::ip::tcp::resolver resolver(io_context);
|
||||
auto endpoints = resolver.resolve("127.0.0.1", "20013");
|
||||
asio::connect(socket, endpoints);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Socket goes out of scope and closes
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Player 0 should still exist in the session map even after disconnect
|
||||
EXPECT_NO_THROW({ server.send(0, "Message after disconnect"); });
|
||||
|
||||
server.stop();
|
||||
if (server_thread.joinable()) {
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user