query/test/core/net/ReactorTest.cpp

266 lines
8.5 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>
#include "matador/net/reactor.hpp"
#include "matador/net/handler.hpp"
#include <chrono>
#include <thread>
#include <atomic>
namespace {
class MockHandler : public matador::net::handler {
public:
explicit MockHandler(int fd = 1) : fd_(fd) {
reset_counters();
}
void reset_counters() {
read_count = 0;
write_count = 0;
timeout_count = 0;
close_count = 0;
}
socket_type handle() const override { return fd_; }
bool is_ready_read() const override { return ready_read_; }
bool is_ready_write() const override { return ready_write_; }
void on_input() override { ++read_count; }
void on_output() override { ++write_count; }
void on_timeout() override { ++timeout_count; }
void close() override { ++close_count; }
void set_ready_states(bool read, bool write) {
ready_read_ = read;
ready_write_ = write;
}
static std::atomic<int> read_count;
static std::atomic<int> write_count;
static std::atomic<int> timeout_count;
static std::atomic<int> close_count;
private:
int fd_;
bool ready_read_{false};
bool ready_write_{false};
};
std::atomic<int> MockHandler::read_count{0};
std::atomic<int> MockHandler::write_count{0};
std::atomic<int> MockHandler::timeout_count{0};
std::atomic<int> MockHandler::close_count{0};
class ReactorTestFixture {
protected:
void SetUp() {
MockHandler::read_count = 0;
MockHandler::write_count = 0;
MockHandler::timeout_count = 0;
MockHandler::close_count = 0;
}
matador::net::reactor reactor_;
};
} // namespace
SCENARIO_METHOD(ReactorTestFixture, "Reactor Handler Registration", "[reactor]") {
GIVEN("A reactor and a handler") {
auto handler = std::make_shared<MockHandler>();
WHEN("Registering handler for read events") {
reactor_.register_handler(handler, matador::net::event_type::READ_MASK);
THEN("Handler should be registered") {
auto fdsets = reactor_.fd_sets();
REQUIRE(fdsets.read_set().is_set(handler->handle()));
REQUIRE_FALSE(fdsets.write_set().is_set(handler->handle()));
}
}
WHEN("Registering handler for write events") {
handler->set_ready_states(false, true);
reactor_.register_handler(handler, matador::net::event_type::WRITE_MASK);
THEN("Handler should be registered for write") {
auto fdsets = reactor_.fd_sets();
REQUIRE(fdsets.write_set().is_set(handler->handle()));
REQUIRE_FALSE(fdsets.read_set().is_set(handler->handle()));
}
}
WHEN("Registering null handler") {
THEN("Should throw exception") {
REQUIRE_THROWS_AS(
reactor_.register_handler(nullptr, matador::net::event_type::READ_MASK),
std::invalid_argument
);
}
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Handler Unregistration", "[reactor]") {
GIVEN("A reactor with registered handler") {
auto handler = std::make_shared<MockHandler>();
reactor_.register_handler(handler, matador::event_type::READ_MASK);
WHEN("Unregistering the handler") {
reactor_.unregister_handler(handler, matador::event_type::READ_MASK);
THEN("Handler should be unregistered") {
auto fdsets = reactor_.fdsets();
REQUIRE_FALSE(fdsets.read_set().is_set(handler->handle()));
REQUIRE(MockHandler::close_count == 1);
}
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Timer Operations", "[reactor]") {
GIVEN("A reactor and a handler") {
auto handler = std::make_shared<MockHandler>();
WHEN("Scheduling a timer") {
reactor_.schedule_timer(handler, 1, 0);
THEN("Timer should be scheduled") {
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
reactor_.handle_events();
REQUIRE(MockHandler::timeout_count == 1);
}
}
WHEN("Scheduling a repeating timer") {
reactor_.schedule_timer(handler, 1, 1);
THEN("Timer should fire multiple times") {
std::this_thread::sleep_for(std::chrono::milliseconds(3500));
reactor_.handle_events();
REQUIRE(MockHandler::timeout_count >= 2);
}
}
WHEN("Cancelling a timer") {
reactor_.schedule_timer(handler, 2, 0);
reactor_.cancel_timer(handler);
THEN("Timer should not fire") {
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
reactor_.handle_events();
REQUIRE(MockHandler::timeout_count == 0);
}
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Event Handling", "[reactor]") {
GIVEN("A reactor with registered handlers") {
auto read_handler = std::make_shared<MockHandler>(1);
auto write_handler = std::make_shared<MockHandler>(2);
read_handler->set_ready_states(true, false);
write_handler->set_ready_states(false, true);
reactor_.register_handler(read_handler, matador::event_type::READ_MASK);
reactor_.register_handler(write_handler, matador::event_type::WRITE_MASK);
WHEN("Handling events") {
std::thread reactor_thread([this]() {
reactor_.run();
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
THEN("Events should be processed") {
REQUIRE(MockHandler::read_count > 0);
REQUIRE(MockHandler::write_count > 0);
}
reactor_.shutdown();
reactor_thread.join();
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Shutdown Behavior", "[reactor]") {
GIVEN("A running reactor") {
auto handler = std::make_shared<MockHandler>();
reactor_.register_handler(handler, matador::event_type::READ_MASK);
std::thread reactor_thread([this]() {
reactor_.run();
});
WHEN("Shutting down the reactor") {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
reactor_.shutdown();
reactor_thread.join();
THEN("Reactor should stop cleanly") {
REQUIRE_FALSE(reactor_.is_running());
REQUIRE(MockHandler::close_count == 1);
}
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Stress Test", "[reactor][stress]") {
GIVEN("A reactor with multiple handlers") {
std::vector<std::shared_ptr<MockHandler>> handlers;
const int NUM_HANDLERS = 100;
for (int i = 0; i < NUM_HANDLERS; ++i) {
auto handler = std::make_shared<MockHandler>(i + 1);
handler->set_ready_states(true, true);
handlers.push_back(handler);
reactor_.register_handler(handler,
matador::event_type::READ_MASK | matador::event_type::WRITE_MASK);
}
WHEN("Running under load") {
std::thread reactor_thread([this]() {
reactor_.run();
});
std::this_thread::sleep_for(std::chrono::seconds(2));
THEN("Should handle events without issues") {
REQUIRE(MockHandler::read_count > 0);
REQUIRE(MockHandler::write_count > 0);
REQUIRE(reactor_.is_running());
}
reactor_.shutdown();
reactor_thread.join();
THEN("Should clean up properly") {
REQUIRE(MockHandler::close_count == NUM_HANDLERS);
}
}
}
}
SCENARIO_METHOD(ReactorTestFixture, "Reactor Error Handling", "[reactor]") {
GIVEN("A reactor with faulty handler") {
class FaultyHandler : public MockHandler {
void on_input() override { throw std::runtime_error("Simulated error"); }
};
auto handler = std::make_shared<FaultyHandler>();
handler->set_ready_states(true, false);
WHEN("Handling events with error") {
reactor_.register_handler(handler, matador::event_type::READ_MASK);
THEN("Should handle error gracefully") {
reactor_.handle_events();
REQUIRE(reactor_.get_statistics().errors_ > 0);
}
}
}
}