From 5c9b5074026f82698d15f633a6d65a3d614a7694 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 08:01:39 -0300 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Implement=20R?= =?UTF-8?q?eadWithReadFail=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libserial/serial.hpp | 31 ++++++++++++++++++-- src/serial.cpp | 57 ++++++++++++++++-------------------- test/test_serial_pty.cpp | 36 +++++++++++++++++++++++ 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/include/libserial/serial.hpp b/include/libserial/serial.hpp index 4c3ebcd..04139af 100644 --- a/include/libserial/serial.hpp +++ b/include/libserial/serial.hpp @@ -8,13 +8,16 @@ #include #include #include +#include #include #include #include +#include #include #include #include +#include #include #include #include @@ -227,7 +230,7 @@ void setDataLength(DataLength nbits); * @param parity The desired parity setting (ENABLE or DISABLE) * @throws SerialException if parity cannot be set */ -void setParity([[maybe_unused]] Parity parity); +void setParity(Parity parity); /** * @brief Sets the stop bits configuration @@ -237,7 +240,7 @@ void setParity([[maybe_unused]] Parity parity); * @param stop_bits The desired stop bits setting (ONE, ONE_AND_HALF, or TWO) * @throws SerialException if stop bits cannot be set */ -void setStopBits([[maybe_unused]] StopBits stop_bits); +void setStopBits(StopBits stop_bits); /** * @brief Sets the flow control configuration @@ -315,9 +318,33 @@ int getBaudRate() const; void setFdForTest(int fd) { fd_serial_port_ = fd; } + +// For testing - allow injection of mock functions +void setSystemCallFunctions( + std::function poll_func, + std::function read_func) { + poll_ = [poll_func](struct pollfd* f, nfds_t n, int t) { + return poll_func(f, n, t); + }; + read_ = [read_func](int fd, void* buf, size_t sz) { + return read_func(fd, buf, sz); + }; +} #endif private: +// Function pointers for system calls +std::function poll_ = + [](struct pollfd* f, nfds_t n, int t) { + return ::poll(f, n, t); + }; + +std::function read_ = + [](int fd, void* buf, size_t sz) { + return ::read(fd, buf, sz); + }; + + /** * @brief Applies terminal settings to the port * diff --git a/src/serial.cpp b/src/serial.cpp index 29f92c8..8a84d7c 100644 --- a/src/serial.cpp +++ b/src/serial.cpp @@ -73,7 +73,7 @@ size_t Serial::read(std::shared_ptr buffer) { // 0 => no wait (immediate return), -1 => block forever, positive => wait specified milliseconds int timeout_ms = static_cast(read_timeout_ms_.count()); - int pr = poll(&fd_poll, 1, timeout_ms); + int pr = poll_(&fd_poll, 1, timeout_ms); if (pr < 0) { if (errno == EINTR) { throw IOException("Interrupted while polling"); @@ -85,7 +85,7 @@ size_t Serial::read(std::shared_ptr buffer) { } // Data available: do the read - ssize_t bytes_read = ::read(fd_serial_port_, const_cast(buffer->data()), kMaxSafeReadSize); + ssize_t bytes_read = read_(fd_serial_port_, const_cast(buffer->data()), kMaxSafeReadSize); if (bytes_read < 0) { throw IOException(std::string("Error reading from serial port: ") + strerror(errno)); } @@ -157,7 +157,7 @@ size_t Serial::readUntil(std::shared_ptr buffer, char terminator) { int64_t remaining_timeout = read_timeout_ms_.count() - elapsed; int timeout_ms = static_cast(remaining_timeout); - int poll_result = poll(&pfd, 1, timeout_ms); + int poll_result = poll_(&pfd, 1, timeout_ms); if (poll_result < 0) { throw IOException("Error in poll(): " + std::string(strerror(errno))); } @@ -167,7 +167,7 @@ size_t Serial::readUntil(std::shared_ptr buffer, char terminator) { } // Data is available, perform the read - ssize_t bytes_read = ::read(fd_serial_port_, &temp_char, 1); + ssize_t bytes_read = read_(fd_serial_port_, &temp_char, 1); if (bytes_read < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { @@ -246,35 +246,30 @@ void Serial::setDataLength(DataLength nbits) { this->setTermios2(); } -void Serial::setParity([[maybe_unused]] Parity parity) { -// this->getTermios2(); -// switch (parity) { -// case Parity::DISABLE: -// options_.c_cflag &= ~PARENB; -// break; -// case Parity::ENABLE: -// options_.c_cflag |= PARENB; -// break; -// default: -// options_.c_cflag &= ~PARENB; -// break; -// } -// this->setTermios2(); +void Serial::setParity(Parity parity) { + this->getTermios2(); + switch (parity) { + case Parity::DISABLE: + options_.c_cflag &= ~PARENB; + break; + case Parity::ENABLE: + options_.c_cflag |= PARENB; + break; + } + this->setTermios2(); } -void Serial::setStopBits([[maybe_unused]] StopBits stop_bits) { -// this->getTermios2(); -// switch (stop_bits) { -// case StopBits::DISABLE: -// options_.c_cflag &= ~CSTOP; -// break; -// case StopBits::ENABLE: -// options_.c_cflag |= CSTOP; -// default: -// options_.c_cflag &= ~CSTOP; -// break; -// } -// this->setTermios2(); +void Serial::setStopBits(StopBits stop_bits) { + this->getTermios2(); + switch (stop_bits) { + case StopBits::ONE: + options_.c_cflag &= ~CSTOP; + break; + case StopBits::TWO: + options_.c_cflag |= CSTOP; + break; + } + this->setTermios2(); } void Serial::setFlowControl([[maybe_unused]] FlowControl flow_control) { diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 003afa4..47ec861 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -293,6 +293,42 @@ TEST_F(PseudoTerminalTest, ReadTimeout) { }, libserial::IOException); } +TEST_F(PseudoTerminalTest, ReadWithReadFail) { + libserial::Serial serial_port; + auto read_buffer = std::make_shared(); + + const std::vector > errorScenarios = { + {EBADF, "Bad file descriptor"}, + {EIO, "Input/output error"}, + {EINTR, "Interrupted system call"}, + {EAGAIN, "Resource temporarily unavailable"}, + {EISDIR, "Is a directory"} + }; + + for (const auto& [error_num, error_msg] : errorScenarios) { + serial_port.setSystemCallFunctions( + [](struct pollfd*, nfds_t, int) -> int { + return 1; + }, + [error_num](int, void*, size_t) -> ssize_t { + errno = error_num; + return -1; + }); + + auto expected_what = "Error reading from serial port: " + error_msg; + + EXPECT_THROW({ + try { + serial_port.read(read_buffer); + } + catch (const libserial::IOException& e) { + EXPECT_STREQ(expected_what.c_str(), e.what()); + throw; + } + }, libserial::IOException); + } +} + TEST_F(PseudoTerminalTest, ReadBytesNonCanonicalMode) { libserial::Serial serial_port; From 8215acbecaaf946b29e8a81ab80177794e3c32fd Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 08:18:36 -0300 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Remove=20GTES?= =?UTF-8?q?T=5FLOGs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/serial.cpp | 4 ++-- test/test_ports.cpp | 1 - test/test_serial_pty.cpp | 13 ++++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/serial.cpp b/src/serial.cpp index 8a84d7c..6ed6859 100644 --- a/src/serial.cpp +++ b/src/serial.cpp @@ -81,7 +81,7 @@ size_t Serial::read(std::shared_ptr buffer) { throw IOException(std::string("Error in poll(): ") + strerror(errno)); } if (pr == 0) { - throw IOException("Read operation timed out after " + std::to_string(timeout_ms) + "ms"); + throw IOException("Read operation timed out after " + std::to_string(timeout_ms) + " milliseconds"); } // Data available: do the read @@ -104,7 +104,7 @@ size_t Serial::readBytes(std::shared_ptr buffer, size_t num_bytes) } if (num_bytes == 0) { - throw IOException("Invalid number of bytes requested"); + throw IOException("Number of bytes requested must be greater than zero"); } buffer->clear(); diff --git a/test/test_ports.cpp b/test/test_ports.cpp index 8f9a165..c12f2ed 100644 --- a/test/test_ports.cpp +++ b/test/test_ports.cpp @@ -57,7 +57,6 @@ TEST_F(PortsTest, ScanPortsThrowsWhenPathMissing) { FAIL() << "Expected libserial::SerialException to be thrown"; } catch (const libserial::SerialException& e) { - GTEST_LOG_(INFO) << "Exception: " << e.what(); // Optionally assert something about the message: EXPECT_NE(std::string(e.what()).find("Error while reading"), std::string::npos); } diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 47ec861..63c0e1e 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -278,16 +278,19 @@ TEST_F(PseudoTerminalTest, ReadTimeout) { serial_port.setBaudRate(9600); // Set a short read timeout - serial_port.setReadTimeout(std::chrono::milliseconds(100)); + int time_out_ms = 100; + serial_port.setReadTimeout(std::chrono::milliseconds(time_out_ms)); auto read_buffer = std::make_shared(); + auto expected_what = "Read operation timed out after " + std::to_string(time_out_ms) + " milliseconds"; + EXPECT_THROW({ try { serial_port.read(read_buffer); } catch (const libserial::IOException& e) { - GTEST_LOG_(INFO) << "Exception: " << e.what(); + EXPECT_STREQ(expected_what.c_str(), e.what()); throw; } }, libserial::IOException); @@ -369,7 +372,7 @@ TEST_F(PseudoTerminalTest, ReadBytesWithNullBuffer) { serial_port.readBytes(null_buffer, 10); } catch (const libserial::IOException& e) { - GTEST_LOG_(INFO) << "Exception: " << e.what(); + EXPECT_STREQ("Null pointer passed to readBytes function", e.what()); throw; } }, libserial::IOException); @@ -389,7 +392,7 @@ TEST_F(PseudoTerminalTest, ReadBytesWithInvalidNumBytes) { serial_port.readBytes(read_buffer, 0); } catch (const libserial::IOException& e) { - GTEST_LOG_(INFO) << "Exception: " << e.what(); + EXPECT_STREQ("Number of bytes requested must be greater than zero", e.what()); throw; } }, libserial::IOException); @@ -410,7 +413,7 @@ TEST_F(PseudoTerminalTest, ReadBytesCanonicalMode) { ADD_FAILURE() << "Expected SerialException but no exception was thrown"; } catch (const libserial::IOException& e) { - GTEST_LOG_(INFO) << "Exception: " << e.what(); + EXPECT_STREQ("readBytes() is not supported in canonical mode; use read() or readUntil() instead", e.what()); throw; } }, libserial::IOException); From 621bcc71b634993be61ecfc233e841872d20e21a Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 08:19:52 -0300 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=8E=A8=20style(lint):=20Fix=20style?= =?UTF-8?q?=20erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/serial.cpp | 3 ++- test/test_serial_pty.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/serial.cpp b/src/serial.cpp index 6ed6859..2eda9d2 100644 --- a/src/serial.cpp +++ b/src/serial.cpp @@ -81,7 +81,8 @@ size_t Serial::read(std::shared_ptr buffer) { throw IOException(std::string("Error in poll(): ") + strerror(errno)); } if (pr == 0) { - throw IOException("Read operation timed out after " + std::to_string(timeout_ms) + " milliseconds"); + throw IOException("Read operation timed out after " + std::to_string(timeout_ms) + + " milliseconds"); } // Data available: do the read diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 63c0e1e..c2efbd0 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -283,7 +283,8 @@ TEST_F(PseudoTerminalTest, ReadTimeout) { auto read_buffer = std::make_shared(); - auto expected_what = "Read operation timed out after " + std::to_string(time_out_ms) + " milliseconds"; + auto expected_what = "Read operation timed out after " + std::to_string(time_out_ms) + + " milliseconds"; EXPECT_THROW({ try { @@ -413,7 +414,9 @@ TEST_F(PseudoTerminalTest, ReadBytesCanonicalMode) { ADD_FAILURE() << "Expected SerialException but no exception was thrown"; } catch (const libserial::IOException& e) { - EXPECT_STREQ("readBytes() is not supported in canonical mode; use read() or readUntil() instead", e.what()); + EXPECT_STREQ( + "readBytes() is not supported in canonical mode; use read() or readUntil() instead", + e.what()); throw; } }, libserial::IOException); From 9ab0ee1c5fcf6334d1cc9ccb40fe104790428edd Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 08:25:58 -0300 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=93=9D=20docs(serial):=20Add=20desc?= =?UTF-8?q?riprion=20comments=20to=20system=20function=20wrappers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libserial/serial.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/libserial/serial.hpp b/include/libserial/serial.hpp index 04139af..18889af 100644 --- a/include/libserial/serial.hpp +++ b/include/libserial/serial.hpp @@ -333,18 +333,26 @@ void setSystemCallFunctions( #endif private: -// Function pointers for system calls +/** + * @brief Poll system call function wrapper + * + * Allows injection of custom poll function for testing. + */ std::function poll_ = [](struct pollfd* f, nfds_t n, int t) { return ::poll(f, n, t); }; +/** + * @brief Read system call function wrapper + * + * Allows injection of custom read function for testing. + */ std::function read_ = [](int fd, void* buf, size_t sz) { return ::read(fd, buf, sz); }; - /** * @brief Applies terminal settings to the port * From 315da2cb7b11b7d56c585150ccd8482df5bf9dec Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 08:30:17 -0300 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=8E=A8=20style(lint):=20Fix=20style?= =?UTF-8?q?=20erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libserial/serial.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/libserial/serial.hpp b/include/libserial/serial.hpp index 18889af..5483122 100644 --- a/include/libserial/serial.hpp +++ b/include/libserial/serial.hpp @@ -335,7 +335,7 @@ void setSystemCallFunctions( private: /** * @brief Poll system call function wrapper - * + * * Allows injection of custom poll function for testing. */ std::function poll_ = @@ -345,7 +345,7 @@ std::function poll_ = /** * @brief Read system call function wrapper - * + * * Allows injection of custom read function for testing. */ std::function read_ = From f8e4484fa723912934eeb55e18a37b5aba5a8aa3 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 09:18:28 -0300 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Implement=20R?= =?UTF-8?q?eadWithPollFail=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/serial.cpp | 3 --- test/test_serial_pty.cpp | 42 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/serial.cpp b/src/serial.cpp index 2eda9d2..24f698a 100644 --- a/src/serial.cpp +++ b/src/serial.cpp @@ -75,9 +75,6 @@ size_t Serial::read(std::shared_ptr buffer) { int timeout_ms = static_cast(read_timeout_ms_.count()); int pr = poll_(&fd_poll, 1, timeout_ms); if (pr < 0) { - if (errno == EINTR) { - throw IOException("Interrupted while polling"); - } throw IOException(std::string("Error in poll(): ") + strerror(errno)); } if (pr == 0) { diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index c2efbd0..3b91765 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -301,7 +301,7 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - const std::vector > errorScenarios = { + const std::vector > error_scenarios = { {EBADF, "Bad file descriptor"}, {EIO, "Input/output error"}, {EINTR, "Interrupted system call"}, @@ -309,7 +309,7 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { {EISDIR, "Is a directory"} }; - for (const auto& [error_num, error_msg] : errorScenarios) { + for (const auto& [error_num, error_msg] : error_scenarios) { serial_port.setSystemCallFunctions( [](struct pollfd*, nfds_t, int) -> int { return 1; @@ -333,6 +333,44 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { } } +TEST_F(PseudoTerminalTest, ReadWithPollFail) { + libserial::Serial serial_port; + auto read_buffer = std::make_shared(); + + const std::vector > error_scenarios = { + {EAGAIN, "Resource temporarily unavailable"}, + {ENOMEM, "Cannot allocate memory"}, + {EINVAL, "Invalid argument"}, + {EPERM, "Operation not permitted"}, + {EBADF, "Bad file descriptor"}, + {EEXIST, "File exists"}, + {ENOENT, "No such file or directory"} + }; + + for (const auto& [error_num, error_msg] : error_scenarios) { + serial_port.setSystemCallFunctions( + [error_num](struct pollfd*, nfds_t, int) -> int { + errno = error_num; + return -1; + }, + [](int, void*, size_t) -> ssize_t { + return 1; + }); + + auto expected_what = "Error in poll(): " + error_msg; + + EXPECT_THROW({ + try { + serial_port.read(read_buffer); + } + catch (const libserial::IOException& e) { + EXPECT_STREQ(expected_what.c_str(), e.what()); + throw; + } + }, libserial::IOException); + } +} + TEST_F(PseudoTerminalTest, ReadBytesNonCanonicalMode) { libserial::Serial serial_port; From 4b4929bbaf0a675698f06dedb673d8ae705dd462 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 09:22:39 -0300 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Add=20EINTR?= =?UTF-8?q?=20to=20ReadWithPollFail=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 3b91765..93da0c3 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -344,7 +344,8 @@ TEST_F(PseudoTerminalTest, ReadWithPollFail) { {EPERM, "Operation not permitted"}, {EBADF, "Bad file descriptor"}, {EEXIST, "File exists"}, - {ENOENT, "No such file or directory"} + {ENOENT, "No such file or directory"}, + {EINTR, "Interrupted system call"} }; for (const auto& [error_num, error_msg] : error_scenarios) { From 6785043a91bf1b8c39f8bed78bb1cfa6c8be3cae Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 09:54:25 -0300 Subject: [PATCH 08/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Include=20ass?= =?UTF-8?q?erts=20for=20input=20vectors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 93da0c3..7c6b865 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -309,6 +309,8 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { {EISDIR, "Is a directory"} }; + ASSERT_EQ(error_scenarios.size(), 5); + for (const auto& [error_num, error_msg] : error_scenarios) { serial_port.setSystemCallFunctions( [](struct pollfd*, nfds_t, int) -> int { @@ -348,6 +350,8 @@ TEST_F(PseudoTerminalTest, ReadWithPollFail) { {EINTR, "Interrupted system call"} }; + ASSERT_EQ(error_scenarios.size(), 8); + for (const auto& [error_num, error_msg] : error_scenarios) { serial_port.setSystemCallFunctions( [error_num](struct pollfd*, nfds_t, int) -> int { From 836bcbe9a1531971e2b1de463ed0a34bbe2f1641 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 10:02:25 -0300 Subject: [PATCH 09/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Move=20input?= =?UTF-8?q?=20erros=20vector=20for=20setup=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 7c6b865..8c0759e 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -57,12 +57,35 @@ void SetUp() override { FAIL() << "Failed to open slave pseudo-terminal"; return; } + + erros_poll_ = { + {EAGAIN, "Resource temporarily unavailable"}, + {ENOMEM, "Cannot allocate memory"}, + {EINVAL, "Invalid argument"}, + {EPERM, "Operation not permitted"}, + {EBADF, "Bad file descriptor"}, + {EEXIST, "File exists"}, + {ENOENT, "No such file or directory"}, + {EINTR, "Interrupted system call"} + }; + + erros_read_ = { + {EBADF, "Bad file descriptor"}, + {EIO, "Input/output error"}, + {EINTR, "Interrupted system call"}, + {EAGAIN, "Resource temporarily unavailable"}, + {EISDIR, "Is a directory"} + }; } void TearDown() override { if (master_fd_ != -1) close(master_fd_); if (slave_fd_ != -1) close(slave_fd_); } + +std::vector> erros_poll_; +std::vector> erros_read_; + }; TEST_F(PseudoTerminalTest, OpenClosePort) { @@ -301,17 +324,7 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - const std::vector > error_scenarios = { - {EBADF, "Bad file descriptor"}, - {EIO, "Input/output error"}, - {EINTR, "Interrupted system call"}, - {EAGAIN, "Resource temporarily unavailable"}, - {EISDIR, "Is a directory"} - }; - - ASSERT_EQ(error_scenarios.size(), 5); - - for (const auto& [error_num, error_msg] : error_scenarios) { + for (const auto& [error_num, error_msg] : erros_read_) { serial_port.setSystemCallFunctions( [](struct pollfd*, nfds_t, int) -> int { return 1; @@ -339,20 +352,7 @@ TEST_F(PseudoTerminalTest, ReadWithPollFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - const std::vector > error_scenarios = { - {EAGAIN, "Resource temporarily unavailable"}, - {ENOMEM, "Cannot allocate memory"}, - {EINVAL, "Invalid argument"}, - {EPERM, "Operation not permitted"}, - {EBADF, "Bad file descriptor"}, - {EEXIST, "File exists"}, - {ENOENT, "No such file or directory"}, - {EINTR, "Interrupted system call"} - }; - - ASSERT_EQ(error_scenarios.size(), 8); - - for (const auto& [error_num, error_msg] : error_scenarios) { + for (const auto& [error_num, error_msg] : erros_poll_) { serial_port.setSystemCallFunctions( [error_num](struct pollfd*, nfds_t, int) -> int { errno = error_num; From 2c612ab55c158bd275aaf68b138c4b6562c77531 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 10:13:58 -0300 Subject: [PATCH 10/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Implement=20R?= =?UTF-8?q?eadUntilWithPollFail=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 116 ++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 8c0759e..3a782fe 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -150,50 +150,6 @@ TEST_F(PseudoTerminalTest, GetAvailableData) { EXPECT_EQ(available, bytes_written); } -TEST_F(PseudoTerminalTest, ReadUntil) { - libserial::Serial serial_port; - - serial_port.open(slave_port_); - serial_port.setBaudRate(9600); - - const std::string test_message = "Read Until! Test!\n"; - - ssize_t bytes_written = write(master_fd_, test_message.c_str(), test_message.length()); - ASSERT_GT(bytes_written, 0) << "Failed to write to master end"; - - // Give time for data to propagate - fsync(master_fd_); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - // Test reading with shared pointer - only read what's available - auto read_buffer = std::make_shared(); - - EXPECT_NO_THROW({serial_port.readUntil(read_buffer, '!'); }); - - EXPECT_EQ(*read_buffer, "Read Until!"); -} - -TEST_F(PseudoTerminalTest, ReadUntilTimeout) { - libserial::Serial serial_port; - - serial_port.open(slave_port_); - serial_port.setBaudRate(9600); - - const std::string test_message = "Read Until Test"; - - ssize_t bytes_written = write(master_fd_, test_message.c_str(), test_message.length()); - ASSERT_GT(bytes_written, 0) << "Failed to write to master end"; - - // Give time for data to propagate - fsync(master_fd_); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - // Test reading with shared pointer - only read what's available - auto read_buffer = std::make_shared(); - - EXPECT_THROW({serial_port.readUntil(read_buffer, '!'); }, libserial::IOException); -} - TEST_F(PseudoTerminalTest, WriteTest) { libserial::Serial serial_port; @@ -464,3 +420,75 @@ TEST_F(PseudoTerminalTest, ReadBytesCanonicalMode) { } }, libserial::IOException); } + +TEST_F(PseudoTerminalTest, ReadUntil) { + libserial::Serial serial_port; + + serial_port.open(slave_port_); + serial_port.setBaudRate(9600); + + const std::string test_message = "Read Until! Test!\n"; + + ssize_t bytes_written = write(master_fd_, test_message.c_str(), test_message.length()); + ASSERT_GT(bytes_written, 0) << "Failed to write to master end"; + + // Give time for data to propagate + fsync(master_fd_); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Test reading with shared pointer - only read what's available + auto read_buffer = std::make_shared(); + + EXPECT_NO_THROW({serial_port.readUntil(read_buffer, '!'); }); + + EXPECT_EQ(*read_buffer, "Read Until!"); +} + +TEST_F(PseudoTerminalTest, ReadUntilTimeout) { + libserial::Serial serial_port; + + serial_port.open(slave_port_); + serial_port.setBaudRate(9600); + + const std::string test_message = "Read Until Test"; + + ssize_t bytes_written = write(master_fd_, test_message.c_str(), test_message.length()); + ASSERT_GT(bytes_written, 0) << "Failed to write to master end"; + + // Give time for data to propagate + fsync(master_fd_); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Test reading with shared pointer - only read what's available + auto read_buffer = std::make_shared(); + + EXPECT_THROW({serial_port.readUntil(read_buffer, '!'); }, libserial::IOException); +} + +TEST_F(PseudoTerminalTest, ReadUntilWithPollFail) { + libserial::Serial serial_port; + auto read_buffer = std::make_shared(); + + for (const auto& [error_num, error_msg] : erros_poll_) { + serial_port.setSystemCallFunctions( + [error_num](struct pollfd*, nfds_t, int) -> int { + errno = error_num; + return -1; + }, + [](int, void*, size_t) -> ssize_t { + return 1; + }); + + auto expected_what = "Error in poll(): " + error_msg; + + EXPECT_THROW({ + try { + serial_port.readUntil(read_buffer, '!'); + } + catch (const libserial::IOException& e) { + EXPECT_STREQ(expected_what.c_str(), e.what()); + throw; + } + }, libserial::IOException); + } +} From 2e71e75e04ffc88b9b1138fe5f454d74a86a6aa7 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 10:24:55 -0300 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=8E=A8=20style(lint):=20Fix=20style?= =?UTF-8?q?=20erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 3a782fe..8e8490c 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -74,6 +74,7 @@ void SetUp() override { {EIO, "Input/output error"}, {EINTR, "Interrupted system call"}, {EAGAIN, "Resource temporarily unavailable"}, + {EWOULDBLOCK, "Resource temporarily unavailable"}, {EISDIR, "Is a directory"} }; } @@ -83,9 +84,8 @@ void TearDown() override { if (slave_fd_ != -1) close(slave_fd_); } -std::vector> erros_poll_; -std::vector> erros_read_; - +std::vector > erros_poll_; +std::vector > erros_read_; }; TEST_F(PseudoTerminalTest, OpenClosePort) { @@ -125,6 +125,34 @@ TEST_F(PseudoTerminalTest, SetAndGetBaudRate) { serial_port.close(); } +TEST_F(PseudoTerminalTest, SetParity) { + libserial::Serial serial_port; + + serial_port.open(slave_port_); + + // Set parity to ENABLE + EXPECT_NO_THROW({ serial_port.setParity(libserial::Parity::ENABLE); }); + + // Set parity to ODD + EXPECT_NO_THROW({ serial_port.setParity(libserial::Parity::DISABLE); }); + + serial_port.close(); +} + +TEST_F(PseudoTerminalTest, SetStopBits) { + libserial::Serial serial_port; + + serial_port.open(slave_port_); + + // Set stop bits to 1 + EXPECT_NO_THROW({ serial_port.setStopBits(libserial::StopBits::ONE); }); + + // Set stop bits to 2 + EXPECT_NO_THROW({ serial_port.setStopBits(libserial::StopBits::TWO); }); + + serial_port.close(); +} + TEST_F(PseudoTerminalTest, GetAvailableData) { libserial::Serial serial_port; From 63afb0fb4c20bb979e714de9dcd0fd54389569ed Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 10:55:26 -0300 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Implement=20R?= =?UTF-8?q?eadUntilWithReadFail=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/serial.cpp | 4 ++-- test/test_serial_pty.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/serial.cpp b/src/serial.cpp index 24f698a..ff70861 100644 --- a/src/serial.cpp +++ b/src/serial.cpp @@ -173,11 +173,11 @@ size_t Serial::readUntil(std::shared_ptr buffer, char terminator) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } - throw SerialException("Error reading from serial port: " + std::string(strerror(errno))); + throw IOException("Error reading from serial port: " + std::string(strerror(errno))); } else if (bytes_read == 0) { // End of file or connection closed - throw SerialException("Connection closed while reading"); + throw IOException("Connection closed while reading: no terminator found"); } // Add the character to buffer (including terminator) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index 8e8490c..ae603a8 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -493,6 +493,38 @@ TEST_F(PseudoTerminalTest, ReadUntilTimeout) { EXPECT_THROW({serial_port.readUntil(read_buffer, '!'); }, libserial::IOException); } +TEST_F(PseudoTerminalTest, ReadUntilWithReadFail) { + libserial::Serial serial_port; + auto read_buffer = std::make_shared(); + + for (const auto& [error_num, error_msg] : erros_read_) { + if (error_num == EAGAIN || error_num == EWOULDBLOCK) { + // Skip these as they are handled differently in readUntil + continue; + } + serial_port.setSystemCallFunctions( + [](struct pollfd*, nfds_t, int) -> int { + return 1; + }, + [error_num](int, void*, size_t) -> ssize_t { + errno = error_num; + return -1; + }); + + auto expected_what = "Error reading from serial port: " + error_msg; + + EXPECT_THROW({ + try { + serial_port.readUntil(read_buffer, '!'); + } + catch (const libserial::IOException& e) { + EXPECT_STREQ(expected_what.c_str(), e.what()); + throw; + } + }, libserial::IOException); + } +} + TEST_F(PseudoTerminalTest, ReadUntilWithPollFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); From ef25be4016f20bcb96028996d163bc895a5f02c1 Mon Sep 17 00:00:00 2001 From: Nestor Neto Date: Sat, 25 Oct 2025 11:01:52 -0300 Subject: [PATCH 13/13] =?UTF-8?q?=E2=9C=85=20test(serial):=20Fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_serial_pty.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_serial_pty.cpp b/test/test_serial_pty.cpp index ae603a8..89047db 100644 --- a/test/test_serial_pty.cpp +++ b/test/test_serial_pty.cpp @@ -58,7 +58,7 @@ void SetUp() override { return; } - erros_poll_ = { + errors_poll_ = { {EAGAIN, "Resource temporarily unavailable"}, {ENOMEM, "Cannot allocate memory"}, {EINVAL, "Invalid argument"}, @@ -69,7 +69,7 @@ void SetUp() override { {EINTR, "Interrupted system call"} }; - erros_read_ = { + errors_read_ = { {EBADF, "Bad file descriptor"}, {EIO, "Input/output error"}, {EINTR, "Interrupted system call"}, @@ -84,8 +84,8 @@ void TearDown() override { if (slave_fd_ != -1) close(slave_fd_); } -std::vector > erros_poll_; -std::vector > erros_read_; +std::vector > errors_poll_; +std::vector > errors_read_; }; TEST_F(PseudoTerminalTest, OpenClosePort) { @@ -308,7 +308,7 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - for (const auto& [error_num, error_msg] : erros_read_) { + for (const auto& [error_num, error_msg] : errors_read_) { serial_port.setSystemCallFunctions( [](struct pollfd*, nfds_t, int) -> int { return 1; @@ -336,7 +336,7 @@ TEST_F(PseudoTerminalTest, ReadWithPollFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - for (const auto& [error_num, error_msg] : erros_poll_) { + for (const auto& [error_num, error_msg] : errors_poll_) { serial_port.setSystemCallFunctions( [error_num](struct pollfd*, nfds_t, int) -> int { errno = error_num; @@ -497,7 +497,7 @@ TEST_F(PseudoTerminalTest, ReadUntilWithReadFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - for (const auto& [error_num, error_msg] : erros_read_) { + for (const auto& [error_num, error_msg] : errors_read_) { if (error_num == EAGAIN || error_num == EWOULDBLOCK) { // Skip these as they are handled differently in readUntil continue; @@ -529,7 +529,7 @@ TEST_F(PseudoTerminalTest, ReadUntilWithPollFail) { libserial::Serial serial_port; auto read_buffer = std::make_shared(); - for (const auto& [error_num, error_msg] : erros_poll_) { + for (const auto& [error_num, error_msg] : errors_poll_) { serial_port.setSystemCallFunctions( [error_num](struct pollfd*, nfds_t, int) -> int { errno = error_num;