]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4141] added unittests
authorRazvan Becheriu <razvan@isc.org>
Wed, 29 Oct 2025 20:01:25 +0000 (22:01 +0200)
committerRazvan Becheriu <razvan@isc.org>
Mon, 3 Nov 2025 18:59:34 +0000 (18:59 +0000)
src/hooks/dhcp/high_availability/libloadtests/close_unittests.cc
src/lib/dhcp/iface_mgr.cc
src/lib/dhcp/select_event_handler.cc
src/lib/dhcp/select_event_handler.h
src/lib/dhcp/tests/fd_event_handler_factory_unittests.cc [new file with mode: 0644]
src/lib/dhcp/tests/fd_event_handler_unittests.h [new file with mode: 0644]
src/lib/dhcp/tests/meson.build
src/lib/dhcp/tests/select_event_handler_unittests.cc [new file with mode: 0644]
src/lib/util/ready_check.cc

index cfed042a37e2fecafadb1138624c39e4b9489da5..741fc9c271c2edd8cc646bb639635b72690d80a2 100644 (file)
@@ -228,44 +228,29 @@ CloseHATest::runPartners(bool const backup /* = true */) {
         }
 
         wthread_->markReady(WatchedThread::READY);
+        FDEventHandlerPtr ev_handler_ = FDEventHandlerFactory::factoryFDEventHandler();
 
         for (;;) {
-            int nfd;
-            fd_set fds;
-            FD_ZERO(&fds);
-            FD_SET(wthread_->getWatchFd(WatchedThread::TERMINATE), &fds);
-            nfd = wthread_->getWatchFd(WatchedThread::TERMINATE);
-            FD_SET(accept_partner1, &fds);
-            if (accept_partner1 > nfd) {
-                nfd = accept_partner1;
-            }
-            FD_SET(accept_partner2, &fds);
-            if (accept_partner2 > nfd) {
-                nfd = accept_partner2;
-            }
+            ev_handler_->clear();
+            ev_handler_->add(wthread_->getWatchFd(WatchedThread::TERMINATE));
+            ev_handler_->add(accept_partner1);
+            ev_handler_->add(accept_partner2);
             for (auto const& reader : readers) {
                 if (!reader.second) {
                     continue;
                 }
-                int fd = reader.first;
-                FD_SET(fd, &fds);
-                if (fd > nfd) {
-                    nfd = fd;
-                }
+                ev_handler_->add(reader.first);
             }
-            struct timeval tm;
-            tm.tv_sec = tm.tv_usec = 0;
-            // @todo implement this using SelectEventHandler
-            // @todo: should move to epoll/kqueue?
-            int n = select(nfd + 1, &fds, 0, 0, &tm);
+
+            int n = ev_handler_->waitEvent(0, 0);
             if ((n < 0) && (errno == EINTR)) {
                 cerr << "interrupted" << endl;
                 continue;
             }
-            if (FD_ISSET(wthread_->getWatchFd(WatchedThread::TERMINATE), &fds)) {
+            if (ev_handler_->readReady(wthread_->getWatchFd(WatchedThread::TERMINATE))) {
                 break;
             }
-            if (FD_ISSET(accept_partner1, &fds)) {
+            if (ev_handler_->readReady(accept_partner1)) {
                 int fd = accept(accept_partner1, 0, 0);
                 if (fd < 0) {
                     cerr << "accept1 failed " << strerror(errno) << endl;
@@ -276,7 +261,7 @@ CloseHATest::runPartners(bool const backup /* = true */) {
                     readers[fd] = true;
                 }
             }
-            if (FD_ISSET(accept_partner2, &fds)) {
+            if (ev_handler_->readReady(accept_partner2)) {
                 int fd = accept(accept_partner2, 0, 0);
                 if (fd < 0) {
                     cerr << "accept2 failed " << strerror(errno) << endl;
@@ -292,7 +277,7 @@ CloseHATest::runPartners(bool const backup /* = true */) {
                     continue;
                 }
                 int fd = reader.first;
-                if (FD_ISSET(fd, &fds)) {
+                if (ev_handler_->readReady(fd)) {
                     char buf[128];
                     int cc = read(fd, buf, 128);
                     if (cc < 0) {
index 351c29229def58095d893f0bd30331d71c8c4233..cec5c896e9c3e67fb9bf656c4daf380223e61a6a 100644 (file)
@@ -1258,6 +1258,8 @@ Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /*
     /// @todo: marginal performance optimization. We could create the set once
     /// and then use its copy for select(). Please note that select() modifies
     /// provided set to indicated which sockets have something to read.
+    /// @note: this can be achieved with FDEventHandler (initialize only if max_fd_ is 0)
+    /// and do not clear the state.
     for (const IfacePtr& iface : ifaces_) {
         for (const SocketInfo& s : iface->getSockets()) {
             // Only deal with IPv4 addresses.
@@ -1340,6 +1342,7 @@ Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /*
     }
 
     // Let's find out which interface/socket has the data
+    // @todo: fix iface starvation
     IfacePtr recv_if;
     for (const IfacePtr& iface : ifaces_) {
         for (const SocketInfo& s : iface->getSockets()) {
@@ -1387,6 +1390,8 @@ IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ )
     /// @todo: marginal performance optimization. We could create the set once
     /// and then use its copy for select(). Please note that select() modifies
     /// provided set to indicated which sockets have something to read.
+    /// @note: this can be achieved with FDEventHandler (initialize only if max_fd_ is 0)
+    /// and do not clear the state.
     for (const IfacePtr& iface : ifaces_) {
         for (const SocketInfo& s : iface->getSockets()) {
             // Only deal with IPv6 addresses.
@@ -1469,6 +1474,7 @@ IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ )
     }
 
     // Let's find out which interface/socket has the data
+    // @todo: fix iface starvation
     for (const IfacePtr& iface : ifaces_) {
         for (const SocketInfo& s : iface->getSockets()) {
             if (fd_event_handler_->readReady(s.sockfd_)) {
index 1810834e84252bf0fe88863d90209c473cadb735..0d63f6e57928ee369f6898f10ea1659e1846f055 100644 (file)
@@ -43,7 +43,6 @@ void SelectEventHandler::add(int fd, bool read /* = true */, bool write /* = fal
     }
 }
 
-// @brief Wait for events on registered file descriptors.
 int SelectEventHandler::waitEvent(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
     // Sanity check for microsecond timeout.
     if (timeout_usec >= 1000000) {
@@ -54,28 +53,18 @@ int SelectEventHandler::waitEvent(uint32_t timeout_sec, uint32_t timeout_usec /*
     select_timeout.tv_sec = timeout_sec;
     select_timeout.tv_usec = timeout_usec;
 
-    FD_COPY(&read_fd_set_, &ready_read_fd_set_);
-    FD_COPY(&write_fd_set_, &ready_write_fd_set_);
+    FD_COPY(&read_fd_set_, &read_fd_set_data_);
+    FD_COPY(&write_fd_set_, &write_fd_set_data_);
 
-    return (select(max_fd_ + 1, &ready_read_fd_set_, &ready_write_fd_set_, 0, &select_timeout));
+    return (select(max_fd_ + 1, &read_fd_set_data_, &write_fd_set_data_, 0, &select_timeout));
 }
 
-// @brief Check if file descriptor is ready for read operation.
-//
-// @param fd The file descriptor.
-//
-// @return True if file descriptor is ready for reading.
 bool SelectEventHandler::readReady(int fd) {
-    return (FD_ISSET(fd, &ready_read_fd_set_));
+    return (FD_ISSET(fd, &read_fd_set_data_));
 }
 
-// @brief Check if file descriptor is ready for write operation.
-//
-// @param fd The file descriptor.
-//
-// @return True if file descriptor is ready for writing.
 bool SelectEventHandler::writeReady(int fd) {
-    return (FD_ISSET(fd, &ready_write_fd_set_));
+    return (FD_ISSET(fd, &write_fd_set_data_));
 }
 
 void SelectEventHandler::clear() {
index ab51510d43346e4d78b9ae78d50ceb8508bdd872..4dd8a38a492ae1393bdc1cf2e47f03deace5e70c 100644 (file)
@@ -7,7 +7,7 @@
 #ifndef SELECT_EVENT_HANDLER_H
 #define SELECT_EVENT_HANDLER_H
 
-#include <fd_event_handler.h>
+#include <dhcp/fd_event_handler.h>
 
 #include <sys/select.h>
 
@@ -69,10 +69,10 @@ private:
     fd_set write_fd_set_;
 
     /// @brief The read event FD set.
-    fd_set ready_read_fd_set_;
+    fd_set read_fd_set_data_;
 
     /// @brief The write event FD set.
-    fd_set ready_write_fd_set_;
+    fd_set write_fd_set_data_;
 };
 
 }  // namespace isc::dhcp
diff --git a/src/lib/dhcp/tests/fd_event_handler_factory_unittests.cc b/src/lib/dhcp/tests/fd_event_handler_factory_unittests.cc
new file mode 100644 (file)
index 0000000..4539ad4
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp/fd_event_handler_factory.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <sys/select.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(FDEventHandlerFactory, factory) {
+    FDEventHandlerPtr handler = FDEventHandlerFactory::factoryFDEventHandler();
+    EXPECT_EQ(handler->type(), FDEventHandler::TYPE_SELECT);
+    EXPECT_THROW(handler->add(FD_SETSIZE), BadValue);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/fd_event_handler_unittests.h b/src/lib/dhcp/tests/fd_event_handler_unittests.h
new file mode 100644 (file)
index 0000000..e41523e
--- /dev/null
@@ -0,0 +1,135 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp/fd_event_handler.h>
+#include <dhcp/select_event_handler.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+const unsigned char MARKER = 0;
+
+namespace {
+
+// Just a NOP function to ignore a signal but let it interrupt function.
+void no_handler(int) { }
+
+/// @brief Shared test fixture for testing code using fd event handlers.
+class FDEventHandlerTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    FDEventHandlerTest() {
+        handler_.reset(new isc::dhcp::FDEventHandlerType);
+        pipe(pipefd);
+    }
+
+    /// @brief Destructor.
+    ~FDEventHandlerTest() {
+        close(pipefd[1]);
+        close(pipefd[0]);
+    }
+
+    /// @brief The tested fd event handler.
+    FDEventHandlerPtr handler_;
+
+    /// @brief The pipe used tor testing read and write operations.
+    int pipefd[2];
+};
+
+TEST_F(FDEventHandlerTest, events) {
+    EXPECT_NO_THROW(handler_->clear());
+
+    EXPECT_EQ(0, handler_->waitEvent(0, 1000));
+
+    handler_->add(pipefd[0], true, false);
+    handler_->add(pipefd[1], false, true);
+
+    EXPECT_FALSE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[1]));
+
+    EXPECT_EQ(1, handler_->waitEvent(0, 1000));
+
+    EXPECT_FALSE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+
+    EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+
+    EXPECT_EQ(2, handler_->waitEvent(0, 1000));
+
+    EXPECT_TRUE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+
+    EXPECT_EQ(1, write(pipefd[1], &MARKER, sizeof(MARKER)));
+
+    EXPECT_EQ(2, handler_->waitEvent(0, 1000));
+
+    EXPECT_TRUE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+
+    unsigned char data;
+
+    EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+
+    EXPECT_EQ(2, handler_->waitEvent(0, 1000));
+
+    EXPECT_TRUE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+
+    EXPECT_EQ(1, read(pipefd[0], &data, sizeof(data)));
+
+    EXPECT_EQ(1, handler_->waitEvent(0, 1000));
+
+    EXPECT_FALSE(handler_->readReady(pipefd[0]));
+    EXPECT_FALSE(handler_->writeReady(pipefd[0]));
+    EXPECT_FALSE(handler_->readReady(pipefd[1]));
+    EXPECT_TRUE(handler_->writeReady(pipefd[1]));
+
+    EXPECT_NO_THROW(handler_->clear());
+
+    EXPECT_EQ(0, handler_->waitEvent(0, 1000));
+
+    struct sigaction ignored, original;
+    memset(&ignored, 0, sizeof ignored);
+    ignored.sa_handler = no_handler;
+    sigaction(SIGINT, &ignored, &original);
+
+    auto tid = pthread_self();
+    sigset_t sset;
+    sigset_t osset;
+    sigemptyset(&sset);
+    sigaddset(&sset, SIGINT);
+    pthread_sigmask(SIG_BLOCK, &sset, &osset);
+    std::thread tr([&tid]() {
+        usleep(500);
+        pthread_kill(tid, SIGINT);
+    });
+
+    pthread_sigmask(SIG_SETMASK, &osset, 0);
+
+    EXPECT_EQ(-1, handler_->waitEvent(1, 0));
+
+    EXPECT_EQ(errno, EINTR);
+
+    tr.join();
+
+    sigaction(SIGINT, &original, NULL);
+}
+
+} // end of anonymous namespace
index b08f6ca9acc87165272035251cb23626ce4ec476..9a66ab0734ef639e5f2203cc7d902e0ad9270374 100644 (file)
@@ -17,6 +17,7 @@ kea_dhcp_tests = executable(
     'classify_unittest.cc',
     'duid_factory_unittest.cc',
     'duid_unittest.cc',
+    'fd_event_handler_factory_unittests.cc',
     'hwaddr_unittest.cc',
     'iface_mgr_unittest.cc',
     'libdhcp++_unittest.cc',
@@ -61,6 +62,7 @@ kea_dhcp_tests = executable(
     'protocol_util_unittest.cc',
     'run_unittests.cc',
     pkt_filter_xpf_unittest_cc,
+    'select_event_handler_unittests.cc',
     dependencies: [GTEST_DEP, CRYPTO_DEP],
     cpp_args: [f'-DTEST_DATA_BUILDDIR="@current_build_dir@"'],
     include_directories: [include_directories('.')] + INCLUDES,
diff --git a/src/lib/dhcp/tests/select_event_handler_unittests.cc b/src/lib/dhcp/tests/select_event_handler_unittests.cc
new file mode 100644 (file)
index 0000000..3a2f2ee
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#define FDEventHandlerType SelectEventHandler
+#define FDEventHandlerTest SelectEventHandlerTest
+
+#include <fd_event_handler_unittests.h>
index 1e7aeca8b28911097be07d7d150078392ddc8bde..29d835606e0dc301cfec9e18e2c6d61a880884fe 100644 (file)
@@ -13,7 +13,7 @@ namespace util {
 
 int selectCheck(const int fd_to_check, const unsigned int timeout_sec,
                 bool read_check, bool write_check) {
-    // @todo implement this using SelectEventHandler
+    // @todo: implement this using SelectEventHandler
     // @todo: should move to epoll/kqueue?
     if (fd_to_check < 0) {
         return (-1);