266 lines
8.5 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|
|
} |