first pass on tests
This commit is contained in:
parent
14a395aa3c
commit
4fd6027132
158
tests/test_buffer.cpp
Normal file
158
tests/test_buffer.cpp
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#include "../lib/Buffer.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void test_basic_operations()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic buffer operations...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf;
|
||||||
|
assert(buf.readableBytes() == 0);
|
||||||
|
assert(buf.writableBytes() > 0);
|
||||||
|
|
||||||
|
std::string data = "Hello World";
|
||||||
|
buf.append(data);
|
||||||
|
assert(buf.readableBytes() == data.size());
|
||||||
|
|
||||||
|
std::string result = buf.read(5);
|
||||||
|
assert(result == "Hello");
|
||||||
|
assert(buf.readableBytes() == 6);
|
||||||
|
|
||||||
|
std::string remaining = buf.readAll();
|
||||||
|
assert(remaining == " World");
|
||||||
|
assert(buf.readableBytes() == 0);
|
||||||
|
|
||||||
|
std::cout << "✓ Basic operations passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_network_byte_order()
|
||||||
|
{
|
||||||
|
std::cout << "Testing network byte order operations...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf;
|
||||||
|
|
||||||
|
uint8_t val8 = 0x42;
|
||||||
|
uint16_t val16 = 0x1234;
|
||||||
|
uint32_t val32 = 0x12345678;
|
||||||
|
uint64_t val64 = 0x123456789ABCDEF0;
|
||||||
|
|
||||||
|
buf.appendInt8(val8);
|
||||||
|
buf.appendInt16(val16);
|
||||||
|
buf.appendInt32(val32);
|
||||||
|
buf.appendInt64(val64);
|
||||||
|
|
||||||
|
assert(buf.readInt8() == val8);
|
||||||
|
assert(buf.readInt16() == val16);
|
||||||
|
assert(buf.readInt32() == val32);
|
||||||
|
assert(buf.readInt64() == val64);
|
||||||
|
|
||||||
|
std::cout << "✓ Network byte order passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prepend_operations()
|
||||||
|
{
|
||||||
|
std::cout << "Testing prepend operations...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf;
|
||||||
|
buf.append("World");
|
||||||
|
|
||||||
|
std::string hello = "Hello ";
|
||||||
|
buf.prepend(hello.data(), hello.size());
|
||||||
|
|
||||||
|
std::string result = buf.readAll();
|
||||||
|
assert(result == "Hello World");
|
||||||
|
|
||||||
|
reactor::Buffer buf2;
|
||||||
|
buf2.append("Test");
|
||||||
|
buf2.prependInt32(42);
|
||||||
|
|
||||||
|
assert(buf2.readInt32() == 42);
|
||||||
|
assert(buf2.read(4) == "Test");
|
||||||
|
|
||||||
|
std::cout << "✓ Prepend operations passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_buffer_growth()
|
||||||
|
{
|
||||||
|
std::cout << "Testing buffer growth...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf(64);
|
||||||
|
std::string large_data(2048, 'X');
|
||||||
|
|
||||||
|
buf.append(large_data);
|
||||||
|
assert(buf.readableBytes() == large_data.size());
|
||||||
|
|
||||||
|
std::string result = buf.readAll();
|
||||||
|
assert(result == large_data);
|
||||||
|
|
||||||
|
std::cout << "✓ Buffer growth passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_buffer_compaction()
|
||||||
|
{
|
||||||
|
std::cout << "Testing buffer compaction...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf(128);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
buf.append("data");
|
||||||
|
buf.read(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(buf.readableBytes() == 20);
|
||||||
|
|
||||||
|
buf.append("more data");
|
||||||
|
assert(buf.readableBytes() == 29);
|
||||||
|
|
||||||
|
std::cout << "✓ Buffer compaction passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_peek_operations()
|
||||||
|
{
|
||||||
|
std::cout << "Testing peek operations...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf;
|
||||||
|
buf.appendInt32(0x12345678);
|
||||||
|
buf.appendInt16(0xABCD);
|
||||||
|
|
||||||
|
assert(buf.peekInt32() == 0x12345678);
|
||||||
|
assert(buf.readableBytes() == 6);
|
||||||
|
|
||||||
|
buf.readInt32();
|
||||||
|
assert(buf.peekInt16() == 0xABCD);
|
||||||
|
assert(buf.readableBytes() == 2);
|
||||||
|
|
||||||
|
std::cout << "✓ Peek operations passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_buffer_swap()
|
||||||
|
{
|
||||||
|
std::cout << "Testing buffer swap...\n";
|
||||||
|
|
||||||
|
reactor::Buffer buf1, buf2;
|
||||||
|
buf1.append("Buffer1");
|
||||||
|
buf2.append("Buffer2");
|
||||||
|
|
||||||
|
buf1.swap(buf2);
|
||||||
|
|
||||||
|
assert(buf1.readAll() == "Buffer2");
|
||||||
|
assert(buf2.readAll() == "Buffer1");
|
||||||
|
|
||||||
|
std::cout << "✓ Buffer swap passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== Buffer Tests ===\n";
|
||||||
|
|
||||||
|
test_basic_operations();
|
||||||
|
test_network_byte_order();
|
||||||
|
test_prepend_operations();
|
||||||
|
test_buffer_growth();
|
||||||
|
test_buffer_compaction();
|
||||||
|
test_peek_operations();
|
||||||
|
test_buffer_swap();
|
||||||
|
|
||||||
|
std::cout << "All buffer tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
301
tests/test_core.cpp
Normal file
301
tests/test_core.cpp
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
#include "../lib/Core.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <sys/eventfd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
class TestEventLoop
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unique_ptr<reactor::EventLoop> loop_;
|
||||||
|
std::thread thread_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestEventLoop()
|
||||||
|
{
|
||||||
|
thread_ = std::thread([this]() {
|
||||||
|
loop_ = std::make_unique<reactor::EventLoop>();
|
||||||
|
loop_->loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
~TestEventLoop()
|
||||||
|
{
|
||||||
|
if (loop_) {
|
||||||
|
loop_->quit();
|
||||||
|
}
|
||||||
|
if (thread_.joinable()) {
|
||||||
|
thread_.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reactor::EventLoop* getLoop() { return loop_.get(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_timer_basic()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic timer functionality...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
bool timer_fired = false;
|
||||||
|
auto timer_id = loop->runAfter(reactor::Duration(50), [&timer_fired]() {
|
||||||
|
timer_fired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
assert(timer_fired);
|
||||||
|
|
||||||
|
std::cout << "✓ Basic timer passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_timer_cancellation()
|
||||||
|
{
|
||||||
|
std::cout << "Testing timer cancellation...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
bool timer_fired = false;
|
||||||
|
auto timer_id = loop->runAfter(reactor::Duration(100), [&timer_fired]() {
|
||||||
|
timer_fired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
loop->cancel(timer_id);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||||
|
assert(!timer_fired);
|
||||||
|
|
||||||
|
std::cout << "✓ Timer cancellation passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_repeating_timer()
|
||||||
|
{
|
||||||
|
std::cout << "Testing repeating timer...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
auto timer_id = loop->runEvery(reactor::Duration(20), [&count]() {
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(85));
|
||||||
|
loop->cancel(timer_id);
|
||||||
|
|
||||||
|
assert(count >= 3 && count <= 5);
|
||||||
|
std::cout << "✓ Repeating timer passed (count: " << count << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_run_in_loop()
|
||||||
|
{
|
||||||
|
std::cout << "Testing runInLoop functionality...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
bool task_executed = false;
|
||||||
|
loop->runInLoop([&task_executed]() {
|
||||||
|
task_executed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(task_executed);
|
||||||
|
|
||||||
|
std::cout << "✓ runInLoop passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_queue_in_loop()
|
||||||
|
{
|
||||||
|
std::cout << "Testing queueInLoop functionality...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
std::vector<int> execution_order;
|
||||||
|
|
||||||
|
loop->queueInLoop([&execution_order]() {
|
||||||
|
execution_order.push_back(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
loop->queueInLoop([&execution_order]() {
|
||||||
|
execution_order.push_back(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
loop->runInLoop([&execution_order]() {
|
||||||
|
execution_order.push_back(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
assert(execution_order.size() == 3);
|
||||||
|
assert(execution_order[2] == 3);
|
||||||
|
|
||||||
|
std::cout << "✓ queueInLoop passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_channel_basic()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic channel functionality...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
int event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||||
|
assert(event_fd >= 0);
|
||||||
|
|
||||||
|
auto channel = std::make_unique<reactor::Channel>(loop, event_fd);
|
||||||
|
|
||||||
|
bool read_callback_called = false;
|
||||||
|
channel->setReadCallback([&read_callback_called]() {
|
||||||
|
read_callback_called = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
channel->enableReading();
|
||||||
|
|
||||||
|
uint64_t val = 1;
|
||||||
|
write(event_fd, &val, sizeof(val));
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
assert(read_callback_called);
|
||||||
|
|
||||||
|
channel->disableAll();
|
||||||
|
channel->remove();
|
||||||
|
close(event_fd);
|
||||||
|
|
||||||
|
std::cout << "✓ Basic channel passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_channel_write_events()
|
||||||
|
{
|
||||||
|
std::cout << "Testing channel write events...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
int event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||||
|
assert(event_fd >= 0);
|
||||||
|
|
||||||
|
auto channel = std::make_unique<reactor::Channel>(loop, event_fd);
|
||||||
|
|
||||||
|
bool write_callback_called = false;
|
||||||
|
channel->setWriteCallback([&write_callback_called]() {
|
||||||
|
write_callback_called = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
channel->enableWriting();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
assert(write_callback_called);
|
||||||
|
|
||||||
|
channel->disableAll();
|
||||||
|
channel->remove();
|
||||||
|
close(event_fd);
|
||||||
|
|
||||||
|
std::cout << "✓ Channel write events passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_multiple_timers()
|
||||||
|
{
|
||||||
|
std::cout << "Testing multiple timers...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
std::vector<bool> timer_states(5, false);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
loop->runAfter(reactor::Duration(10 + i * 20), [&timer_states, i]() {
|
||||||
|
timer_states[i] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||||
|
|
||||||
|
for (bool state : timer_states) {
|
||||||
|
assert(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Multiple timers passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_timer_precision()
|
||||||
|
{
|
||||||
|
std::cout << "Testing timer precision...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
bool timer_fired = false;
|
||||||
|
|
||||||
|
loop->runAfter(reactor::Duration(100), [&timer_fired, start_time]() {
|
||||||
|
auto end_time = std::chrono::steady_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||||
|
timer_fired = true;
|
||||||
|
|
||||||
|
assert(duration.count() >= 90 && duration.count() <= 150);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||||
|
assert(timer_fired);
|
||||||
|
|
||||||
|
std::cout << "✓ Timer precision passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_event_loop_thread_safety()
|
||||||
|
{
|
||||||
|
std::cout << "Testing event loop thread safety...\n";
|
||||||
|
|
||||||
|
TestEventLoop test_loop;
|
||||||
|
auto loop = test_loop.getLoop();
|
||||||
|
|
||||||
|
std::atomic<int> counter{0};
|
||||||
|
constexpr int num_tasks = 100;
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for (int t = 0; t < 4; ++t) {
|
||||||
|
threads.emplace_back([loop, &counter, num_tasks]() {
|
||||||
|
for (int i = 0; i < num_tasks; ++i) {
|
||||||
|
loop->queueInLoop([&counter]() {
|
||||||
|
counter++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
assert(counter == 4 * num_tasks);
|
||||||
|
|
||||||
|
std::cout << "✓ Event loop thread safety passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== Core Tests ===\n";
|
||||||
|
|
||||||
|
test_timer_basic();
|
||||||
|
test_timer_cancellation();
|
||||||
|
test_repeating_timer();
|
||||||
|
test_run_in_loop();
|
||||||
|
test_queue_in_loop();
|
||||||
|
test_channel_basic();
|
||||||
|
test_channel_write_events();
|
||||||
|
test_multiple_timers();
|
||||||
|
test_timer_precision();
|
||||||
|
test_event_loop_thread_safety();
|
||||||
|
|
||||||
|
std::cout << "All core tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
188
tests/test_inet_address.cpp
Normal file
188
tests/test_inet_address.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
#include "../lib/InetAddress.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
void test_ipv4_construction()
|
||||||
|
{
|
||||||
|
std::cout << "Testing IPv4 address construction...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1(8080);
|
||||||
|
assert(addr1.port() == 8080);
|
||||||
|
assert(!addr1.isIpV6());
|
||||||
|
assert(addr1.toIp() == "0.0.0.0");
|
||||||
|
|
||||||
|
reactor::InetAddress addr2(9000, false, true);
|
||||||
|
assert(addr2.port() == 9000);
|
||||||
|
assert(addr2.toIp() == "127.0.0.1");
|
||||||
|
|
||||||
|
reactor::InetAddress addr3("192.168.1.100", 3000);
|
||||||
|
assert(addr3.port() == 3000);
|
||||||
|
assert(addr3.toIp() == "192.168.1.100");
|
||||||
|
|
||||||
|
std::cout << "✓ IPv4 construction passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_ipv6_construction()
|
||||||
|
{
|
||||||
|
std::cout << "Testing IPv6 address construction...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1(8080, true);
|
||||||
|
assert(addr1.port() == 8080);
|
||||||
|
assert(addr1.isIpV6());
|
||||||
|
assert(addr1.toIp() == "::");
|
||||||
|
|
||||||
|
reactor::InetAddress addr2(9000, true, true);
|
||||||
|
assert(addr2.port() == 9000);
|
||||||
|
assert(addr2.toIp() == "::1");
|
||||||
|
|
||||||
|
reactor::InetAddress addr3("::1", 3000);
|
||||||
|
assert(addr3.port() == 3000);
|
||||||
|
assert(addr3.toIp() == "::1");
|
||||||
|
assert(addr3.isIpV6());
|
||||||
|
|
||||||
|
std::cout << "✓ IPv6 construction passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_address_comparison()
|
||||||
|
{
|
||||||
|
std::cout << "Testing address comparison...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1("127.0.0.1", 8080);
|
||||||
|
reactor::InetAddress addr2("127.0.0.1", 8080);
|
||||||
|
reactor::InetAddress addr3("127.0.0.1", 9000);
|
||||||
|
reactor::InetAddress addr4("192.168.1.1", 8080);
|
||||||
|
|
||||||
|
assert(addr1 == addr2);
|
||||||
|
assert(addr1 != addr3);
|
||||||
|
assert(addr1 != addr4);
|
||||||
|
|
||||||
|
reactor::InetAddress addr5("::1", 8080);
|
||||||
|
reactor::InetAddress addr6("::1", 8080);
|
||||||
|
assert(addr5 == addr6);
|
||||||
|
assert(addr1 != addr5);
|
||||||
|
|
||||||
|
std::cout << "✓ Address comparison passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_address_ordering()
|
||||||
|
{
|
||||||
|
std::cout << "Testing address ordering...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1("127.0.0.1", 8080);
|
||||||
|
reactor::InetAddress addr2("127.0.0.1", 9000);
|
||||||
|
reactor::InetAddress addr3("192.168.1.1", 8080);
|
||||||
|
reactor::InetAddress addr4("::1", 8080);
|
||||||
|
|
||||||
|
std::cout << "addr1 < addr2: " << (addr1 < addr2) << "\n";
|
||||||
|
std::cout << "addr1 < addr3: " << (addr1 < addr3) << "\n";
|
||||||
|
std::cout << "addr1 < addr4: " << (addr1 < addr4) << "\n";
|
||||||
|
|
||||||
|
std::cout << "✓ Address ordering passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_string_conversion()
|
||||||
|
{
|
||||||
|
std::cout << "Testing string conversion...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1("192.168.1.100", 8080);
|
||||||
|
assert(addr1.toIpPort() == "192.168.1.100:8080");
|
||||||
|
|
||||||
|
reactor::InetAddress addr2("::1", 8080);
|
||||||
|
assert(addr2.toIpPort() == "[::1]:8080");
|
||||||
|
|
||||||
|
assert(addr1.familyToString() == "IPv4");
|
||||||
|
assert(addr2.familyToString() == "IPv6");
|
||||||
|
|
||||||
|
std::cout << "✓ String conversion passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_address_conversion()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket address conversion...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr("127.0.0.1", 8080);
|
||||||
|
const sockaddr* sa = addr.getSockAddr();
|
||||||
|
socklen_t len = addr.getSockLen();
|
||||||
|
|
||||||
|
assert(sa != nullptr);
|
||||||
|
assert(len == sizeof(sockaddr_in));
|
||||||
|
|
||||||
|
const sockaddr_in* sa4 = reinterpret_cast<const sockaddr_in*>(sa);
|
||||||
|
assert(sa4->sin_family == AF_INET);
|
||||||
|
assert(ntohs(sa4->sin_port) == 8080);
|
||||||
|
|
||||||
|
std::cout << "✓ Socket address conversion passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_hash_functionality()
|
||||||
|
{
|
||||||
|
std::cout << "Testing hash functionality...\n";
|
||||||
|
|
||||||
|
std::unordered_set<reactor::InetAddress> addr_set;
|
||||||
|
|
||||||
|
addr_set.insert(reactor::InetAddress("127.0.0.1", 8080));
|
||||||
|
addr_set.insert(reactor::InetAddress("127.0.0.1", 9000));
|
||||||
|
addr_set.insert(reactor::InetAddress("192.168.1.1", 8080));
|
||||||
|
addr_set.insert(reactor::InetAddress("::1", 8080));
|
||||||
|
|
||||||
|
assert(addr_set.size() == 4);
|
||||||
|
|
||||||
|
addr_set.insert(reactor::InetAddress("127.0.0.1", 8080));
|
||||||
|
assert(addr_set.size() == 4);
|
||||||
|
|
||||||
|
std::cout << "✓ Hash functionality passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_hostname_resolution()
|
||||||
|
{
|
||||||
|
std::cout << "Testing hostname resolution...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress result;
|
||||||
|
bool success = reactor::InetAddress::resolve("localhost", result);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
std::cout << "Resolved localhost to: " << result.toIpPort() << "\n";
|
||||||
|
assert(result.toIp() == "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool failed = reactor::InetAddress::resolve("invalid.hostname.test", result);
|
||||||
|
assert(!failed);
|
||||||
|
|
||||||
|
std::cout << "✓ Hostname resolution passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_edge_cases()
|
||||||
|
{
|
||||||
|
std::cout << "Testing edge cases...\n";
|
||||||
|
|
||||||
|
reactor::InetAddress addr1(0);
|
||||||
|
assert(addr1.port() == 0);
|
||||||
|
|
||||||
|
reactor::InetAddress addr2(65535);
|
||||||
|
assert(addr2.port() == 65535);
|
||||||
|
|
||||||
|
reactor::InetAddress addr3("0.0.0.0", 0);
|
||||||
|
assert(addr3.toIp() == "0.0.0.0");
|
||||||
|
assert(addr3.port() == 0);
|
||||||
|
|
||||||
|
std::cout << "✓ Edge cases passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== InetAddress Tests ===\n";
|
||||||
|
|
||||||
|
test_ipv4_construction();
|
||||||
|
test_ipv6_construction();
|
||||||
|
test_address_comparison();
|
||||||
|
test_address_ordering();
|
||||||
|
test_string_conversion();
|
||||||
|
test_socket_address_conversion();
|
||||||
|
test_hash_functionality();
|
||||||
|
test_hostname_resolution();
|
||||||
|
test_edge_cases();
|
||||||
|
|
||||||
|
std::cout << "All InetAddress tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
286
tests/test_socket.cpp
Normal file
286
tests/test_socket.cpp
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
#include "../lib/Socket.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
void test_socket_creation()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket creation...\n";
|
||||||
|
|
||||||
|
auto tcp_socket = reactor::Socket::createTcp();
|
||||||
|
assert(tcp_socket.fd() >= 0);
|
||||||
|
|
||||||
|
auto tcp6_socket = reactor::Socket::createTcp(true);
|
||||||
|
assert(tcp6_socket.fd() >= 0);
|
||||||
|
|
||||||
|
auto udp_socket = reactor::Socket::createUdp();
|
||||||
|
assert(udp_socket.fd() >= 0);
|
||||||
|
|
||||||
|
auto udp6_socket = reactor::Socket::createUdp(true);
|
||||||
|
assert(udp6_socket.fd() >= 0);
|
||||||
|
|
||||||
|
std::cout << "✓ Socket creation passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_move()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket move semantics...\n";
|
||||||
|
|
||||||
|
auto socket1 = reactor::Socket::createTcp();
|
||||||
|
int fd = socket1.fd();
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
auto socket2 = std::move(socket1);
|
||||||
|
assert(socket2.fd() == fd);
|
||||||
|
assert(socket1.fd() == -1);
|
||||||
|
|
||||||
|
reactor::Socket socket3 = reactor::Socket::createTcp();
|
||||||
|
int fd3 = socket3.fd();
|
||||||
|
socket3 = std::move(socket2);
|
||||||
|
assert(socket3.fd() == fd);
|
||||||
|
|
||||||
|
std::cout << "✓ Socket move semantics passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_bind_listen()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket bind and listen...\n";
|
||||||
|
|
||||||
|
auto socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress addr(0);
|
||||||
|
|
||||||
|
socket.setReuseAddr(true);
|
||||||
|
socket.bind(addr);
|
||||||
|
socket.listen();
|
||||||
|
|
||||||
|
auto local_addr = reactor::Socket::getLocalAddr(socket.fd());
|
||||||
|
assert(local_addr.port() > 0);
|
||||||
|
|
||||||
|
std::cout << "✓ Socket bind and listen passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_options()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket options...\n";
|
||||||
|
|
||||||
|
auto socket = reactor::Socket::createTcp();
|
||||||
|
|
||||||
|
socket.setReuseAddr(true);
|
||||||
|
socket.setReusePort(true);
|
||||||
|
socket.setTcpNoDelay(true);
|
||||||
|
socket.setKeepAlive(true);
|
||||||
|
socket.setTcpKeepAlive(7200, 30, 9);
|
||||||
|
socket.setRecvBuffer(65536);
|
||||||
|
socket.setSendBuffer(65536);
|
||||||
|
|
||||||
|
std::cout << "✓ Socket options passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_connection()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket connection...\n";
|
||||||
|
|
||||||
|
auto server_socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress server_addr(0);
|
||||||
|
|
||||||
|
server_socket.setReuseAddr(true);
|
||||||
|
server_socket.bind(server_addr);
|
||||||
|
server_socket.listen();
|
||||||
|
|
||||||
|
auto actual_addr = reactor::Socket::getLocalAddr(server_socket.fd());
|
||||||
|
std::cout << "Server listening on: " << actual_addr.toIpPort() << "\n";
|
||||||
|
|
||||||
|
std::thread server_thread([&server_socket]() {
|
||||||
|
reactor::InetAddress peer_addr;
|
||||||
|
int client_fd = server_socket.accept(peer_addr);
|
||||||
|
if (client_fd >= 0) {
|
||||||
|
std::cout << "Server accepted connection from: " << peer_addr.toIpPort() << "\n";
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
ssize_t n = read(client_fd, buffer, sizeof(buffer));
|
||||||
|
if (n > 0) {
|
||||||
|
write(client_fd, buffer, n);
|
||||||
|
}
|
||||||
|
close(client_fd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
auto client_socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress connect_addr("127.0.0.1", actual_addr.port());
|
||||||
|
|
||||||
|
int result = client_socket.connect(connect_addr);
|
||||||
|
if (result == 0 || errno == EINPROGRESS) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
const char* message = "Hello Server";
|
||||||
|
ssize_t sent = client_socket.write(message, strlen(message));
|
||||||
|
assert(sent > 0);
|
||||||
|
|
||||||
|
char response[1024];
|
||||||
|
ssize_t received = client_socket.read(response, sizeof(response));
|
||||||
|
assert(received > 0);
|
||||||
|
assert(strncmp(message, response, sent) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_thread.join();
|
||||||
|
std::cout << "✓ Socket connection passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_udp_socket()
|
||||||
|
{
|
||||||
|
std::cout << "Testing UDP socket operations...\n";
|
||||||
|
|
||||||
|
auto server_socket = reactor::Socket::createUdp();
|
||||||
|
reactor::InetAddress server_addr(0);
|
||||||
|
|
||||||
|
server_socket.setReuseAddr(true);
|
||||||
|
server_socket.bind(server_addr);
|
||||||
|
|
||||||
|
auto actual_addr = reactor::Socket::getLocalAddr(server_socket.fd());
|
||||||
|
std::cout << "UDP server bound to: " << actual_addr.toIpPort() << "\n";
|
||||||
|
|
||||||
|
std::thread server_thread([&server_socket]() {
|
||||||
|
char buffer[1024];
|
||||||
|
reactor::InetAddress client_addr;
|
||||||
|
ssize_t n = server_socket.recvFrom(buffer, sizeof(buffer), client_addr);
|
||||||
|
if (n > 0) {
|
||||||
|
server_socket.sendTo(buffer, n, client_addr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
auto client_socket = reactor::Socket::createUdp();
|
||||||
|
reactor::InetAddress target_addr("127.0.0.1", actual_addr.port());
|
||||||
|
|
||||||
|
const char* message = "UDP Hello";
|
||||||
|
ssize_t sent = client_socket.sendTo(message, strlen(message), target_addr);
|
||||||
|
assert(sent > 0);
|
||||||
|
|
||||||
|
char response[1024];
|
||||||
|
reactor::InetAddress from_addr;
|
||||||
|
ssize_t received = client_socket.recvFrom(response, sizeof(response), from_addr);
|
||||||
|
assert(received > 0);
|
||||||
|
assert(strncmp(message, response, sent) == 0);
|
||||||
|
|
||||||
|
server_thread.join();
|
||||||
|
std::cout << "✓ UDP socket operations passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_shutdown()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket shutdown...\n";
|
||||||
|
|
||||||
|
auto server_socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress server_addr(0);
|
||||||
|
|
||||||
|
server_socket.setReuseAddr(true);
|
||||||
|
server_socket.bind(server_addr);
|
||||||
|
server_socket.listen();
|
||||||
|
|
||||||
|
auto actual_addr = reactor::Socket::getLocalAddr(server_socket.fd());
|
||||||
|
|
||||||
|
std::thread server_thread([&server_socket]() {
|
||||||
|
reactor::InetAddress peer_addr;
|
||||||
|
int client_fd = server_socket.accept(peer_addr);
|
||||||
|
if (client_fd >= 0) {
|
||||||
|
reactor::Socket client_sock(client_fd);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
client_sock.shutdownWrite();
|
||||||
|
close(client_fd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
auto client_socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress connect_addr("127.0.0.1", actual_addr.port());
|
||||||
|
|
||||||
|
int result = client_socket.connect(connect_addr);
|
||||||
|
if (result == 0 || errno == EINPROGRESS) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
ssize_t n = client_socket.read(buffer, sizeof(buffer));
|
||||||
|
assert(n == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_thread.join();
|
||||||
|
std::cout << "✓ Socket shutdown passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_socket_error_handling()
|
||||||
|
{
|
||||||
|
std::cout << "Testing socket error handling...\n";
|
||||||
|
|
||||||
|
auto socket = reactor::Socket::createTcp();
|
||||||
|
|
||||||
|
int error = socket.getSocketError();
|
||||||
|
assert(error == 0);
|
||||||
|
|
||||||
|
reactor::InetAddress invalid_addr("192.0.2.1", 12345);
|
||||||
|
int result = socket.connect(invalid_addr);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
std::cout << "✓ Socket error handling passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_address_retrieval()
|
||||||
|
{
|
||||||
|
std::cout << "Testing address retrieval...\n";
|
||||||
|
|
||||||
|
auto server_socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress server_addr("127.0.0.1", 0);
|
||||||
|
|
||||||
|
server_socket.setReuseAddr(true);
|
||||||
|
server_socket.bind(server_addr);
|
||||||
|
server_socket.listen();
|
||||||
|
|
||||||
|
auto local_addr = reactor::Socket::getLocalAddr(server_socket.fd());
|
||||||
|
assert(local_addr.toIp() == "127.0.0.1");
|
||||||
|
assert(local_addr.port() > 0);
|
||||||
|
|
||||||
|
std::cout << "✓ Address retrieval passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_self_connection_detection()
|
||||||
|
{
|
||||||
|
std::cout << "Testing self connection detection...\n";
|
||||||
|
|
||||||
|
auto socket = reactor::Socket::createTcp();
|
||||||
|
reactor::InetAddress addr("127.0.0.1", 0);
|
||||||
|
|
||||||
|
socket.setReuseAddr(true);
|
||||||
|
socket.bind(addr);
|
||||||
|
|
||||||
|
auto bound_addr = reactor::Socket::getLocalAddr(socket.fd());
|
||||||
|
|
||||||
|
bool is_self = socket.isSelfConnected();
|
||||||
|
std::cout << "Self connected: " << is_self << "\n";
|
||||||
|
|
||||||
|
std::cout << "✓ Self connection detection passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== Socket Tests ===\n";
|
||||||
|
|
||||||
|
test_socket_creation();
|
||||||
|
test_socket_move();
|
||||||
|
test_socket_bind_listen();
|
||||||
|
test_socket_options();
|
||||||
|
test_socket_connection();
|
||||||
|
test_udp_socket();
|
||||||
|
test_socket_shutdown();
|
||||||
|
test_socket_error_handling();
|
||||||
|
test_address_retrieval();
|
||||||
|
test_self_connection_detection();
|
||||||
|
|
||||||
|
std::cout << "All socket tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
406
tests/test_tcp_server.cpp
Normal file
406
tests/test_tcp_server.cpp
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
#include "../lib/TcpServer.hpp"
|
||||||
|
#include "../lib/TcpConnection.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class TestClient
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
reactor::Socket socket_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestClient() : socket_(reactor::Socket::createTcp()) {}
|
||||||
|
|
||||||
|
bool connect(const reactor::InetAddress& addr)
|
||||||
|
{
|
||||||
|
int result = socket_.connect(addr);
|
||||||
|
if (result == 0 || errno == EINPROGRESS) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send(const std::string& data)
|
||||||
|
{
|
||||||
|
ssize_t sent = socket_.write(data.data(), data.size());
|
||||||
|
return sent == static_cast<ssize_t>(data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string receive(size_t max_size = 1024)
|
||||||
|
{
|
||||||
|
char buffer[1024];
|
||||||
|
ssize_t received = socket_.read(buffer, std::min(max_size, sizeof(buffer)));
|
||||||
|
if (received > 0) {
|
||||||
|
return std::string(buffer, received);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void close()
|
||||||
|
{
|
||||||
|
socket_.shutdownWrite();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_tcp_server_basic()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic TCP server...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "TestServer");
|
||||||
|
|
||||||
|
std::atomic<bool> server_started{false};
|
||||||
|
std::atomic<bool> connection_received{false};
|
||||||
|
std::atomic<bool> message_received{false};
|
||||||
|
|
||||||
|
server.setConnectionCallback([&](const reactor::TcpConnectionPtr& conn) {
|
||||||
|
if (conn->connected()) {
|
||||||
|
connection_received = true;
|
||||||
|
std::cout << "New connection: " << conn->name() << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "Connection closed: " << conn->name() << "\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setMessageCallback([&](const reactor::TcpConnectionPtr& conn, reactor::Buffer& buffer) {
|
||||||
|
std::string message = buffer.readAll();
|
||||||
|
std::cout << "Received: " << message << "\n";
|
||||||
|
message_received = true;
|
||||||
|
conn->send("Echo: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop, &server_started]() {
|
||||||
|
server_started = true;
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!server_started) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
TestClient client;
|
||||||
|
bool connected = client.connect(reactor::InetAddress("127.0.0.1", listen_addr.port()));
|
||||||
|
assert(connected);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
assert(connection_received);
|
||||||
|
|
||||||
|
assert(client.send("Hello Server"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
assert(message_received);
|
||||||
|
|
||||||
|
std::string response = client.receive();
|
||||||
|
assert(response == "Echo: Hello Server");
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Basic TCP server passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_multiple_connections()
|
||||||
|
{
|
||||||
|
std::cout << "Testing multiple connections...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "MultiServer");
|
||||||
|
|
||||||
|
std::atomic<int> connection_count{0};
|
||||||
|
std::atomic<int> message_count{0};
|
||||||
|
|
||||||
|
server.setConnectionCallback([&](const reactor::TcpConnectionPtr& conn) {
|
||||||
|
if (conn->connected()) {
|
||||||
|
connection_count++;
|
||||||
|
} else {
|
||||||
|
connection_count--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setMessageCallback([&](const reactor::TcpConnectionPtr& conn, reactor::Buffer& buffer) {
|
||||||
|
std::string message = buffer.readAll();
|
||||||
|
message_count++;
|
||||||
|
conn->send("Response: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop]() {
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
constexpr int num_clients = 5;
|
||||||
|
std::vector<std::unique_ptr<TestClient>> clients;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_clients; ++i) {
|
||||||
|
auto client = std::make_unique<TestClient>();
|
||||||
|
bool connected = client->connect(reactor::InetAddress("127.0.0.1", listen_addr.port()));
|
||||||
|
assert(connected);
|
||||||
|
clients.push_back(std::move(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
assert(connection_count == num_clients);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_clients; ++i) {
|
||||||
|
std::string message = "Message " + std::to_string(i);
|
||||||
|
assert(clients[i]->send(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(message_count == num_clients);
|
||||||
|
|
||||||
|
for (auto& client : clients) {
|
||||||
|
client->close();
|
||||||
|
}
|
||||||
|
clients.clear();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(connection_count == 0);
|
||||||
|
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Multiple connections passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_server_with_thread_pool()
|
||||||
|
{
|
||||||
|
std::cout << "Testing server with thread pool...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "ThreadPoolServer");
|
||||||
|
|
||||||
|
server.setThreadNum(2);
|
||||||
|
|
||||||
|
std::atomic<int> message_count{0};
|
||||||
|
std::vector<std::thread::id> thread_ids;
|
||||||
|
std::mutex thread_ids_mutex;
|
||||||
|
|
||||||
|
server.setMessageCallback([&](const reactor::TcpConnectionPtr& conn, reactor::Buffer& buffer) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(thread_ids_mutex);
|
||||||
|
thread_ids.push_back(std::this_thread::get_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string message = buffer.readAll();
|
||||||
|
message_count++;
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
conn->send("Processed: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop]() {
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
|
||||||
|
constexpr int num_clients = 4;
|
||||||
|
std::vector<std::thread> client_threads;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_clients; ++i) {
|
||||||
|
client_threads.emplace_back([&listen_addr, i]() {
|
||||||
|
TestClient client;
|
||||||
|
bool connected = client.connect(reactor::InetAddress("127.0.0.1", listen_addr.port()));
|
||||||
|
assert(connected);
|
||||||
|
|
||||||
|
std::string message = "Client" + std::to_string(i);
|
||||||
|
assert(client.send(message));
|
||||||
|
|
||||||
|
std::string response = client.receive();
|
||||||
|
assert(response == "Processed: " + message);
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& thread : client_threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(message_count == num_clients);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(thread_ids_mutex);
|
||||||
|
std::set<std::thread::id> unique_threads(thread_ids.begin(), thread_ids.end());
|
||||||
|
assert(unique_threads.size() >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Server with thread pool passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_connection_lifecycle()
|
||||||
|
{
|
||||||
|
std::cout << "Testing connection lifecycle...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "LifecycleServer");
|
||||||
|
|
||||||
|
std::atomic<bool> connected{false};
|
||||||
|
std::atomic<bool> disconnected{false};
|
||||||
|
|
||||||
|
server.setConnectionCallback([&](const reactor::TcpConnectionPtr& conn) {
|
||||||
|
if (conn->connected()) {
|
||||||
|
connected = true;
|
||||||
|
conn->send("Welcome");
|
||||||
|
} else {
|
||||||
|
disconnected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setMessageCallback([](const reactor::TcpConnectionPtr& conn, reactor::Buffer& buffer) {
|
||||||
|
buffer.readAll();
|
||||||
|
conn->shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop]() {
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
TestClient client;
|
||||||
|
bool conn_result = client.connect(reactor::InetAddress("127.0.0.1", listen_addr.port()));
|
||||||
|
assert(conn_result);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
assert(connected);
|
||||||
|
|
||||||
|
std::string welcome = client.receive();
|
||||||
|
assert(welcome == "Welcome");
|
||||||
|
|
||||||
|
assert(client.send("Goodbye"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
assert(disconnected);
|
||||||
|
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Connection lifecycle passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_large_message_handling()
|
||||||
|
{
|
||||||
|
std::cout << "Testing large message handling...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "LargeMessageServer");
|
||||||
|
|
||||||
|
std::atomic<bool> large_message_received{false};
|
||||||
|
|
||||||
|
server.setMessageCallback([&](const reactor::TcpConnectionPtr& conn, reactor::Buffer& buffer) {
|
||||||
|
std::string message = buffer.readAll();
|
||||||
|
if (message.size() > 1000) {
|
||||||
|
large_message_received = true;
|
||||||
|
}
|
||||||
|
conn->send("Received " + std::to_string(message.size()) + " bytes");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop]() {
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
TestClient client;
|
||||||
|
bool connected = client.connect(reactor::InetAddress("127.0.0.1", listen_addr.port()));
|
||||||
|
assert(connected);
|
||||||
|
|
||||||
|
std::string large_message(5000, 'X');
|
||||||
|
assert(client.send(large_message));
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(large_message_received);
|
||||||
|
|
||||||
|
std::string response = client.receive();
|
||||||
|
assert(response == "Received 5000 bytes");
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Large message handling passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_server_stats()
|
||||||
|
{
|
||||||
|
std::cout << "Testing server statistics...\n";
|
||||||
|
|
||||||
|
reactor::EventLoop loop;
|
||||||
|
reactor::InetAddress listen_addr(0);
|
||||||
|
reactor::TcpServer server(&loop, listen_addr, "StatsServer");
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
std::thread server_thread([&loop]() {
|
||||||
|
loop.loop();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
assert(server.numConnections() == 0);
|
||||||
|
|
||||||
|
TestClient client1, client2;
|
||||||
|
assert(client1.connect(reactor::InetAddress("127.0.0.1", listen_addr.port())));
|
||||||
|
assert(client2.connect(reactor::InetAddress("127.0.0.1", listen_addr.port())));
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
assert(server.numConnections() == 2);
|
||||||
|
|
||||||
|
auto connections = server.getConnections();
|
||||||
|
assert(connections.size() == 2);
|
||||||
|
|
||||||
|
client1.close();
|
||||||
|
client2.close();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||||
|
assert(server.numConnections() == 0);
|
||||||
|
|
||||||
|
loop.quit();
|
||||||
|
server_thread.join();
|
||||||
|
|
||||||
|
std::cout << "✓ Server statistics passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== TCP Server Tests ===\n";
|
||||||
|
|
||||||
|
test_tcp_server_basic();
|
||||||
|
test_multiple_connections();
|
||||||
|
test_server_with_thread_pool();
|
||||||
|
test_connection_lifecycle();
|
||||||
|
test_large_message_handling();
|
||||||
|
test_server_stats();
|
||||||
|
|
||||||
|
std::cout << "All TCP server tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
314
tests/test_threading.cpp
Normal file
314
tests/test_threading.cpp
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
#include "../lib/EventLoopThread.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
void test_event_loop_thread_basic()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic EventLoopThread...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThread loop_thread("TestThread");
|
||||||
|
auto loop = loop_thread.getLoop();
|
||||||
|
|
||||||
|
assert(loop != nullptr);
|
||||||
|
assert(loop_thread.name() == "TestThread");
|
||||||
|
|
||||||
|
std::atomic<bool> task_executed{false};
|
||||||
|
loop->runInLoop([&task_executed]() {
|
||||||
|
task_executed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
assert(task_executed);
|
||||||
|
|
||||||
|
std::cout << "✓ Basic EventLoopThread passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_event_loop_thread_timer()
|
||||||
|
{
|
||||||
|
std::cout << "Testing EventLoopThread timer...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThread loop_thread("TimerThread");
|
||||||
|
auto loop = loop_thread.getLoop();
|
||||||
|
|
||||||
|
std::atomic<int> timer_count{0};
|
||||||
|
auto timer_id = loop->runEvery(reactor::Duration(20), [&timer_count]() {
|
||||||
|
timer_count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(85));
|
||||||
|
loop->cancel(timer_id);
|
||||||
|
|
||||||
|
int final_count = timer_count;
|
||||||
|
assert(final_count >= 3 && final_count <= 5);
|
||||||
|
|
||||||
|
std::cout << "✓ EventLoopThread timer passed (count: " << final_count << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_multiple_event_loop_threads()
|
||||||
|
{
|
||||||
|
std::cout << "Testing multiple EventLoopThreads...\n";
|
||||||
|
|
||||||
|
constexpr int num_threads = 3;
|
||||||
|
std::vector<std::unique_ptr<reactor::EventLoopThread>> threads;
|
||||||
|
std::vector<std::atomic<int>*> counters;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_threads; ++i) {
|
||||||
|
std::string name = "Thread-" + std::to_string(i);
|
||||||
|
threads.push_back(std::make_unique<reactor::EventLoopThread>(name));
|
||||||
|
counters.push_back(new std::atomic<int>{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_threads; ++i) {
|
||||||
|
auto loop = threads[i]->getLoop();
|
||||||
|
auto counter = counters[i];
|
||||||
|
|
||||||
|
for (int j = 0; j < 10; ++j) {
|
||||||
|
loop->queueInLoop([counter]() {
|
||||||
|
(*counter)++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
for (int i = 0; i < num_threads; ++i) {
|
||||||
|
assert(*counters[i] == 10);
|
||||||
|
delete counters[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Multiple EventLoopThreads passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_event_loop_thread_pool_basic()
|
||||||
|
{
|
||||||
|
std::cout << "Testing basic EventLoopThreadPool...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(3, "PoolThread");
|
||||||
|
|
||||||
|
assert(pool.size() == 3);
|
||||||
|
assert(pool.getBaseName() == "PoolThread");
|
||||||
|
|
||||||
|
auto loops = pool.getAllLoops();
|
||||||
|
assert(loops.size() == 3);
|
||||||
|
|
||||||
|
for (auto loop : loops) {
|
||||||
|
assert(loop != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Basic EventLoopThreadPool passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_pool_round_robin()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread pool round robin...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(3, "RoundRobin");
|
||||||
|
|
||||||
|
std::vector<reactor::EventLoop*> selected_loops;
|
||||||
|
for (int i = 0; i < 9; ++i) {
|
||||||
|
selected_loops.push_back(pool.getNextLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
assert(selected_loops[i] == selected_loops[i + 3]);
|
||||||
|
assert(selected_loops[i] == selected_loops[i + 6]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Thread pool round robin passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_pool_task_distribution()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread pool task distribution...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(3, "TaskDist");
|
||||||
|
|
||||||
|
std::vector<std::atomic<int>*> counters(3);
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
counters[i] = new std::atomic<int>{0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<reactor::EventLoop*, int> loop_to_index;
|
||||||
|
auto loops = pool.getAllLoops();
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
loop_to_index[loops[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int tasks_per_loop = 10;
|
||||||
|
for (int i = 0; i < 3 * tasks_per_loop; ++i) {
|
||||||
|
auto loop = pool.getNextLoop();
|
||||||
|
int index = loop_to_index[loop];
|
||||||
|
|
||||||
|
loop->queueInLoop([counter = counters[index]]() {
|
||||||
|
(*counter)++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
assert(*counters[i] == tasks_per_loop);
|
||||||
|
delete counters[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Thread pool task distribution passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_empty_thread_pool()
|
||||||
|
{
|
||||||
|
std::cout << "Testing empty thread pool...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(0, "EmptyPool");
|
||||||
|
|
||||||
|
assert(pool.size() == 0);
|
||||||
|
assert(pool.getNextLoop() == nullptr);
|
||||||
|
assert(pool.getAllLoops().empty());
|
||||||
|
|
||||||
|
std::cout << "✓ Empty thread pool passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_pool_concurrent_access()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread pool concurrent access...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(4, "ConcurrentAccess");
|
||||||
|
|
||||||
|
std::atomic<int> total_tasks{0};
|
||||||
|
std::vector<std::thread> client_threads;
|
||||||
|
|
||||||
|
for (int t = 0; t < 8; ++t) {
|
||||||
|
client_threads.emplace_back([&pool, &total_tasks]() {
|
||||||
|
for (int i = 0; i < 25; ++i) {
|
||||||
|
auto loop = pool.getNextLoop();
|
||||||
|
assert(loop != nullptr);
|
||||||
|
|
||||||
|
loop->queueInLoop([&total_tasks]() {
|
||||||
|
total_tasks++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& thread : client_threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
assert(total_tasks == 8 * 25);
|
||||||
|
|
||||||
|
std::cout << "✓ Thread pool concurrent access passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_pool_with_timers()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread pool with timers...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThreadPool pool(2, "TimerPool");
|
||||||
|
|
||||||
|
std::atomic<int> timer_count{0};
|
||||||
|
std::vector<uint64_t> timer_ids;
|
||||||
|
|
||||||
|
auto loops = pool.getAllLoops();
|
||||||
|
for (auto loop : loops) {
|
||||||
|
auto timer_id = loop->runEvery(reactor::Duration(30), [&timer_count]() {
|
||||||
|
timer_count++;
|
||||||
|
});
|
||||||
|
timer_ids.push_back(timer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
for (int i = 0; i < loops.size(); ++i) {
|
||||||
|
loops[i]->cancel(timer_ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int final_count = timer_count;
|
||||||
|
assert(final_count >= 4 && final_count <= 8);
|
||||||
|
|
||||||
|
std::cout << "✓ Thread pool with timers passed (count: " << final_count << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_synchronization()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread synchronization...\n";
|
||||||
|
|
||||||
|
reactor::EventLoopThread loop_thread("SyncThread");
|
||||||
|
auto loop = loop_thread.getLoop();
|
||||||
|
|
||||||
|
std::vector<int> results;
|
||||||
|
std::mutex results_mutex;
|
||||||
|
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
std::promise<void> promise;
|
||||||
|
auto future = promise.get_future();
|
||||||
|
|
||||||
|
loop->queueInLoop([i, &results, &results_mutex, promise = std::move(promise)]() mutable {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(results_mutex);
|
||||||
|
results.push_back(i);
|
||||||
|
}
|
||||||
|
promise.set_value();
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.push_back(std::move(future));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : futures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(results.size() == 10);
|
||||||
|
|
||||||
|
std::cout << "✓ Thread synchronization passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_pool_destruction()
|
||||||
|
{
|
||||||
|
std::cout << "Testing thread pool destruction...\n";
|
||||||
|
|
||||||
|
std::atomic<int> destructor_count{0};
|
||||||
|
|
||||||
|
{
|
||||||
|
reactor::EventLoopThreadPool pool(2, "DestroyTest");
|
||||||
|
|
||||||
|
auto loops = pool.getAllLoops();
|
||||||
|
for (auto loop : loops) {
|
||||||
|
loop->queueInLoop([&destructor_count]() {
|
||||||
|
destructor_count++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(destructor_count == 2);
|
||||||
|
std::cout << "✓ Thread pool destruction passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== Threading Tests ===\n";
|
||||||
|
|
||||||
|
test_event_loop_thread_basic();
|
||||||
|
test_event_loop_thread_timer();
|
||||||
|
test_multiple_event_loop_threads();
|
||||||
|
test_event_loop_thread_pool_basic();
|
||||||
|
test_thread_pool_round_robin();
|
||||||
|
test_thread_pool_task_distribution();
|
||||||
|
test_empty_thread_pool();
|
||||||
|
test_thread_pool_concurrent_access();
|
||||||
|
test_thread_pool_with_timers();
|
||||||
|
test_thread_synchronization();
|
||||||
|
test_thread_pool_destruction();
|
||||||
|
|
||||||
|
std::cout << "All threading tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
249
tests/test_utilities.cpp
Normal file
249
tests/test_utilities.cpp
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
#include "../lib/Utilities.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <vector>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
void test_lock_free_queue()
|
||||||
|
{
|
||||||
|
std::cout << "Testing lock-free queue...\n";
|
||||||
|
|
||||||
|
reactor::LockFreeQueue<int> queue;
|
||||||
|
assert(queue.empty());
|
||||||
|
|
||||||
|
queue.enqueue(1);
|
||||||
|
queue.enqueue(2);
|
||||||
|
queue.enqueue(3);
|
||||||
|
assert(!queue.empty());
|
||||||
|
|
||||||
|
int val;
|
||||||
|
assert(queue.dequeue(val) && val == 1);
|
||||||
|
assert(queue.dequeue(val) && val == 2);
|
||||||
|
assert(queue.dequeue(val) && val == 3);
|
||||||
|
assert(queue.empty());
|
||||||
|
assert(!queue.dequeue(val));
|
||||||
|
|
||||||
|
std::cout << "✓ Lock-free queue basic operations passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lock_free_queue_concurrent()
|
||||||
|
{
|
||||||
|
std::cout << "Testing lock-free queue concurrency...\n";
|
||||||
|
|
||||||
|
reactor::LockFreeQueue<int> queue;
|
||||||
|
constexpr int num_items = 1000;
|
||||||
|
constexpr int num_producers = 4;
|
||||||
|
constexpr int num_consumers = 2;
|
||||||
|
|
||||||
|
std::vector<std::thread> producers;
|
||||||
|
std::vector<std::thread> consumers;
|
||||||
|
std::atomic<int> consumed_count{0};
|
||||||
|
std::atomic<bool> stop_consumers{false};
|
||||||
|
|
||||||
|
for (int p = 0; p < num_producers; ++p) {
|
||||||
|
producers.emplace_back([&queue, p, num_items]() {
|
||||||
|
for (int i = 0; i < num_items; ++i) {
|
||||||
|
queue.enqueue(p * num_items + i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int c = 0; c < num_consumers; ++c) {
|
||||||
|
consumers.emplace_back([&queue, &consumed_count, &stop_consumers]() {
|
||||||
|
int val;
|
||||||
|
while (!stop_consumers) {
|
||||||
|
if (queue.dequeue(val)) {
|
||||||
|
consumed_count++;
|
||||||
|
} else {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& p : producers) {
|
||||||
|
p.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (consumed_count < num_producers * num_items) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_consumers = true;
|
||||||
|
for (auto& c : consumers) {
|
||||||
|
c.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(consumed_count == num_producers * num_items);
|
||||||
|
std::cout << "✓ Lock-free queue concurrency passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_object_pool()
|
||||||
|
{
|
||||||
|
std::cout << "Testing object pool...\n";
|
||||||
|
|
||||||
|
struct TestObject
|
||||||
|
{
|
||||||
|
int value = 42;
|
||||||
|
TestObject() { std::cout << "TestObject constructed\n"; }
|
||||||
|
~TestObject() { std::cout << "TestObject destructed\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto pool = std::make_shared<reactor::ObjectPool<TestObject>>();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto obj1 = pool->getObject();
|
||||||
|
auto obj2 = pool->getObject();
|
||||||
|
assert(obj1->value == 42);
|
||||||
|
assert(obj2->value == 42);
|
||||||
|
assert(obj1.get() != obj2.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto obj3 = pool->getObject();
|
||||||
|
assert(obj3->value == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "✓ Object pool passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_logger()
|
||||||
|
{
|
||||||
|
std::cout << "Testing logger...\n";
|
||||||
|
|
||||||
|
reactor::Logger::setLevel(reactor::LogLevel::DEBUG);
|
||||||
|
|
||||||
|
reactor::Logger(reactor::LogLevel::DEBUG) << "Debug message";
|
||||||
|
reactor::Logger(reactor::LogLevel::INFO) << "Info message with number: " << 42;
|
||||||
|
reactor::Logger(reactor::LogLevel::WARN) << "Warning message";
|
||||||
|
reactor::Logger(reactor::LogLevel::ERROR) << "Error message";
|
||||||
|
|
||||||
|
reactor::Logger::setLevel(reactor::LogLevel::ERROR);
|
||||||
|
reactor::Logger(reactor::LogLevel::DEBUG) << "This should not appear";
|
||||||
|
reactor::Logger(reactor::LogLevel::INFO) << "This should not appear";
|
||||||
|
reactor::Logger(reactor::LogLevel::ERROR) << "This should appear";
|
||||||
|
|
||||||
|
reactor::Logger::setLevel(reactor::LogLevel::DEBUG);
|
||||||
|
|
||||||
|
std::cout << "✓ Logger passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_concurrent_task_queue()
|
||||||
|
{
|
||||||
|
std::cout << "Testing concurrent task queue...\n";
|
||||||
|
|
||||||
|
reactor::ConcurrentTaskQueue queue(2, "TestQueue");
|
||||||
|
assert(queue.getName() == "TestQueue");
|
||||||
|
|
||||||
|
std::atomic<int> counter{0};
|
||||||
|
std::promise<void> all_done;
|
||||||
|
auto future = all_done.get_future();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
queue.runTaskInQueue([&counter, i, &all_done]() {
|
||||||
|
counter++;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
if (counter == 10) {
|
||||||
|
all_done.set_value();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
future.wait();
|
||||||
|
assert(counter == 10);
|
||||||
|
|
||||||
|
std::cout << "✓ Concurrent task queue passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_sync_task()
|
||||||
|
{
|
||||||
|
std::cout << "Testing sync task execution...\n";
|
||||||
|
|
||||||
|
reactor::ConcurrentTaskQueue queue(1, "SyncTest");
|
||||||
|
bool task_executed = false;
|
||||||
|
|
||||||
|
queue.syncTaskInQueue([&task_executed]() {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
task_executed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(task_executed);
|
||||||
|
std::cout << "✓ Sync task execution passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_utility_functions()
|
||||||
|
{
|
||||||
|
std::cout << "Testing utility functions...\n";
|
||||||
|
|
||||||
|
std::size_t seed = 0;
|
||||||
|
reactor::hashCombine(seed, 42);
|
||||||
|
reactor::hashCombine(seed, std::string("test"));
|
||||||
|
assert(seed != 0);
|
||||||
|
|
||||||
|
std::string text = "hello,world,test,data";
|
||||||
|
auto parts = reactor::splitString(text, ",");
|
||||||
|
assert(parts.size() == 4);
|
||||||
|
assert(parts[0] == "hello");
|
||||||
|
assert(parts[1] == "world");
|
||||||
|
assert(parts[2] == "test");
|
||||||
|
assert(parts[3] == "data");
|
||||||
|
|
||||||
|
auto single = reactor::splitString("single", ",");
|
||||||
|
assert(single.size() == 1);
|
||||||
|
assert(single[0] == "single");
|
||||||
|
|
||||||
|
std::cout << "✓ Utility functions passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_network_utilities()
|
||||||
|
{
|
||||||
|
std::cout << "Testing network utilities...\n";
|
||||||
|
|
||||||
|
uint64_t original = 0x123456789ABCDEF0ULL;
|
||||||
|
uint64_t network = reactor::hton64(original);
|
||||||
|
uint64_t back = reactor::ntoh64(network);
|
||||||
|
|
||||||
|
assert(back == original);
|
||||||
|
|
||||||
|
std::cout << "✓ Network utilities passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_non_copyable()
|
||||||
|
{
|
||||||
|
std::cout << "Testing NonCopyable...\n";
|
||||||
|
|
||||||
|
class TestClass : public reactor::NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int value = 42;
|
||||||
|
TestClass() = default;
|
||||||
|
TestClass(TestClass&& other) noexcept : value(other.value) { other.value = 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TestClass obj1;
|
||||||
|
TestClass obj2 = std::move(obj1);
|
||||||
|
assert(obj2.value == 42);
|
||||||
|
assert(obj1.value == 0);
|
||||||
|
|
||||||
|
std::cout << "✓ NonCopyable passed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::cout << "=== Utilities Tests ===\n";
|
||||||
|
|
||||||
|
test_lock_free_queue();
|
||||||
|
test_lock_free_queue_concurrent();
|
||||||
|
test_object_pool();
|
||||||
|
test_logger();
|
||||||
|
test_concurrent_task_queue();
|
||||||
|
test_sync_task();
|
||||||
|
test_utility_functions();
|
||||||
|
test_network_utilities();
|
||||||
|
test_non_copyable();
|
||||||
|
|
||||||
|
std::cout << "All utilities tests passed! ✓\n";
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user