Commit 2e35cebd53a35d3059c893d6d2a35b1e5b29daa4
1 parent
9bfbe366
Working on basic read/write unit test using two virtual serial ports.
Showing
10 changed files
with
327 additions
and
80 deletions
.gitignore
.idea/CppLinuxSerial.iml
0 → 100644
.idea/misc.xml
0 → 100644
.idea/modules.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project version="4"> | ||
| 3 | + <component name="ProjectModuleManager"> | ||
| 4 | + <modules> | ||
| 5 | + <module fileurl="file://$PROJECT_DIR$/.idea/CppLinuxSerial.iml" filepath="$PROJECT_DIR$/.idea/CppLinuxSerial.iml" /> | ||
| 6 | + </modules> | ||
| 7 | + </component> | ||
| 8 | +</project> | ||
| 0 | \ No newline at end of file | 9 | \ No newline at end of file |
.idea/vcs.xml
0 → 100644
CMakeLists.txt
| 1 | cmake_minimum_required(VERSION 3.1.0) | 1 | cmake_minimum_required(VERSION 3.1.0) |
| 2 | project(CppLinuxSerial) | 2 | project(CppLinuxSerial) |
| 3 | 3 | ||
| 4 | -add_definitions(-std=c++11) | 4 | +add_definitions(-std=c++14) |
| 5 | 5 | ||
| 6 | option(BUILD_TESTS "If set to true, unit tests will be build as part of make all." TRUE) | 6 | option(BUILD_TESTS "If set to true, unit tests will be build as part of make all." TRUE) |
| 7 | if (BUILD_TESTS) | 7 | if (BUILD_TESTS) |
include/CppLinuxSerial/SerialPort.hpp
| @@ -19,85 +19,92 @@ | @@ -19,85 +19,92 @@ | ||
| 19 | 19 | ||
| 20 | // User headers | 20 | // User headers |
| 21 | 21 | ||
| 22 | -namespace mn | ||
| 23 | -{ | ||
| 24 | -namespace CppLinuxSerial | ||
| 25 | -{ | 22 | +namespace mn { |
| 23 | + namespace CppLinuxSerial { | ||
| 26 | 24 | ||
| 27 | /// \brief Strongly-typed enumeration of baud rates for use with the SerialPort class | 25 | /// \brief Strongly-typed enumeration of baud rates for use with the SerialPort class |
| 28 | -enum class BaudRate | ||
| 29 | -{ | ||
| 30 | - none, | ||
| 31 | - b9600, | ||
| 32 | - b57600 | ||
| 33 | -}; | 26 | + enum class BaudRate { |
| 27 | + none, | ||
| 28 | + b9600, | ||
| 29 | + b57600 | ||
| 30 | + }; | ||
| 31 | + | ||
| 32 | + enum class State { | ||
| 33 | + UNCONFIGURED, | ||
| 34 | + CLOSED, | ||
| 35 | + OPEN | ||
| 36 | + }; | ||
| 34 | 37 | ||
| 35 | /// \brief SerialPort object is used to perform rx/tx serial communication. | 38 | /// \brief SerialPort object is used to perform rx/tx serial communication. |
| 36 | -class SerialPort | ||
| 37 | -{ | 39 | + class SerialPort { |
| 38 | 40 | ||
| 39 | - public: | ||
| 40 | - /// \brief Default constructor. | ||
| 41 | - SerialPort(); | 41 | + public: |
| 42 | + /// \brief Default constructor. | ||
| 43 | + SerialPort(); | ||
| 42 | 44 | ||
| 43 | - /// \brief Constructor that sets up serial port with all required parameters. | ||
| 44 | - SerialPort(const std::string& device, BaudRate baudRate); | 45 | + /// \brief Constructor that sets up serial port with all required parameters. |
| 46 | + SerialPort(const std::string &device, BaudRate baudRate); | ||
| 45 | 47 | ||
| 46 | - //! @brief Destructor | ||
| 47 | - virtual ~SerialPort(); | 48 | + //! @brief Destructor. Closes serial port if still open. |
| 49 | + virtual ~SerialPort(); | ||
| 48 | 50 | ||
| 49 | - //! @brief Sets the file path to use for communications. The file path must be set before Open() is called, otherwise Open() will return an error. | ||
| 50 | - void SetDevice(const std::string& device); | 51 | + //! @brief Sets the file path to use for communications. The file path must be set before Open() is called, otherwise Open() will return an error. |
| 52 | + void SetDevice(const std::string &device); | ||
| 51 | 53 | ||
| 52 | - void SetBaudRate(BaudRate baudRate); | 54 | + void SetBaudRate(BaudRate baudRate); |
| 53 | 55 | ||
| 54 | - //! @brief Controls what happens when Read() is called. | ||
| 55 | - //! @param numOfCharToWait Minimum number of characters to wait for before returning. Set to 0 for non-blocking mode. | ||
| 56 | - void SetNumCharsToWait(uint32_t numCharsToWait); | 56 | + //! @brief Controls what happens when Read() is called. |
| 57 | + //! @param numOfCharToWait Minimum number of characters to wait for before returning. Set to 0 for non-blocking mode. | ||
| 58 | + void SetNumCharsToWait(uint32_t numCharsToWait); | ||
| 57 | 59 | ||
| 58 | - //! @brief Enables/disables echo. | ||
| 59 | - //! param echoOn Pass in true to enable echo, false to disable echo. | ||
| 60 | - void EnableEcho(bool echoOn); | 60 | + //! @brief Enables/disables echo. |
| 61 | + //! param echoOn Pass in true to enable echo, false to disable echo. | ||
| 62 | + void EnableEcho(bool echoOn); | ||
| 61 | 63 | ||
| 62 | - //! @brief Opens the COM port for use. | ||
| 63 | - //! @throws {std::runtime_error} if filename has not been set. | ||
| 64 | - //! {std::system_error} if system open() operation fails. | ||
| 65 | - //! @note Must call this before you can configure the COM port. | ||
| 66 | - void Open(); | 64 | + //! @brief Opens the COM port for use. |
| 65 | + //! @throws {std::runtime_error} if filename has not been set. | ||
| 66 | + //! {std::system_error} if system open() operation fails. | ||
| 67 | + //! @note Must call this before you can configure the COM port. | ||
| 68 | + void Open(); | ||
| 67 | 69 | ||
| 68 | - /// \brief Configures the tty device as a serial port. | ||
| 69 | - void ConfigureDeviceAsSerialPort(); | 70 | + /// \brief Configures the tty device as a serial port. |
| 71 | + /// \warning Device must be open (valid file descriptor) when this is called. | ||
| 72 | + void ConfigureDeviceAsSerialPort(); | ||
| 70 | 73 | ||
| 71 | - //! @brief Closes the COM port. | ||
| 72 | - void Close(); | 74 | + //! @brief Closes the COM port. |
| 75 | + void Close(); | ||
| 73 | 76 | ||
| 74 | - //! @brief Sends a message over the com port. | ||
| 75 | - //! @param str Reference to an string containing the characters to write to the COM port. | ||
| 76 | - //! @throws {std::runtime_error} if filename has not been set. | ||
| 77 | - //! {std::system_error} if system write() operation fails. | ||
| 78 | - void Write(std::string *str); | 77 | + //! @brief Sends a message over the com port. |
| 78 | + //! @param str Reference to an string containing the characters to write to the COM port. | ||
| 79 | + //! @throws {std::runtime_error} if filename has not been set. | ||
| 80 | + //! {std::system_error} if system write() operation fails. | ||
| 81 | + void Write(std::string *str); | ||
| 79 | 82 | ||
| 80 | - //! @brief Use to read from the COM port. | ||
| 81 | - //! @param str Reference to a string that the read characters from the COM port will be saved to. | ||
| 82 | - //! @throws {std::runtime_error} if filename has not been set. | ||
| 83 | - //! {std::system_error} if system read() operation fails. | ||
| 84 | - void Read(std::string *str); | 83 | + //! @brief Use to read from the COM port. |
| 84 | + //! @param str Reference to a string that the read characters from the COM port will be saved to. | ||
| 85 | + //! @throws {std::runtime_error} if filename has not been set. | ||
| 86 | + //! {std::system_error} if system read() operation fails. | ||
| 87 | + void Read(std::string *str); | ||
| 85 | 88 | ||
| 86 | - private: | ||
| 87 | - std::string device_; | 89 | + private: |
| 88 | 90 | ||
| 89 | - BaudRate baudRate_; | 91 | + /// \brief Keeps track of the serial port's state. |
| 92 | + State state_; | ||
| 90 | 93 | ||
| 91 | - //! @brief The file descriptor for the open file. This gets written to when Open() is called. | ||
| 92 | - int fileDesc; | 94 | + std::string device_; |
| 93 | 95 | ||
| 94 | - //! @brief Returns a populated termios structure for the passed in file descriptor. | ||
| 95 | - termios GetTermios(); | 96 | + BaudRate baudRate_; |
| 96 | 97 | ||
| 97 | - void SetTermios(termios myTermios); | ||
| 98 | -}; | 98 | + //! @brief The file descriptor for the open file. This gets written to when Open() is called. |
| 99 | + int fileDesc_; | ||
| 99 | 100 | ||
| 100 | -} // namespace CppLinuxSerial | 101 | + //! @brief Returns a populated termios structure for the passed in file descriptor. |
| 102 | + termios GetTermios(); | ||
| 103 | + | ||
| 104 | + void SetTermios(termios myTermios); | ||
| 105 | + }; | ||
| 106 | + | ||
| 107 | + } // namespace CppLinuxSerial | ||
| 101 | } // namespace mn | 108 | } // namespace mn |
| 102 | 109 | ||
| 103 | #endif // #ifndef SERIAL_PORT_SERIAL_PORT_H | 110 | #endif // #ifndef SERIAL_PORT_SERIAL_PORT_H |
src/SerialPort.cpp
| 1 | //! | 1 | //! |
| 2 | //! @file SerialPort.cpp | 2 | //! @file SerialPort.cpp |
| 3 | -//! @author Geoffrey Hunter <gbmhunter@gmail.com> () | 3 | +//! @author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja) |
| 4 | //! @created 2014-01-07 | 4 | //! @created 2014-01-07 |
| 5 | //! @last-modified 2017-11-23 | 5 | //! @last-modified 2017-11-23 |
| 6 | //! @brief The main serial port class. | 6 | //! @brief The main serial port class. |
| @@ -22,9 +22,7 @@ | @@ -22,9 +22,7 @@ | ||
| 22 | namespace mn { | 22 | namespace mn { |
| 23 | namespace CppLinuxSerial { | 23 | namespace CppLinuxSerial { |
| 24 | 24 | ||
| 25 | - SerialPort::SerialPort() : | ||
| 26 | - SerialPort("", BaudRate::none) | ||
| 27 | - { | 25 | + SerialPort::SerialPort() { |
| 28 | } | 26 | } |
| 29 | 27 | ||
| 30 | SerialPort::SerialPort(const std::string& device, BaudRate baudRate) { | 28 | SerialPort::SerialPort(const std::string& device, BaudRate baudRate) { |
| @@ -32,14 +30,18 @@ namespace CppLinuxSerial { | @@ -32,14 +30,18 @@ namespace CppLinuxSerial { | ||
| 32 | baudRate_ = baudRate; | 30 | baudRate_ = baudRate; |
| 33 | } | 31 | } |
| 34 | 32 | ||
| 35 | - SerialPort::~SerialPort() | ||
| 36 | - { | ||
| 37 | - | 33 | + SerialPort::~SerialPort() { |
| 34 | + try { | ||
| 35 | + Close(); | ||
| 36 | + } catch(...) { | ||
| 37 | + // We can't do anything about this! | ||
| 38 | + } | ||
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | void SerialPort::SetDevice(const std::string& device) | 41 | void SerialPort::SetDevice(const std::string& device) |
| 41 | { | 42 | { |
| 42 | device_ = device; | 43 | device_ = device; |
| 44 | + ConfigureDeviceAsSerialPort(); | ||
| 43 | } | 45 | } |
| 44 | 46 | ||
| 45 | void SerialPort::SetBaudRate(BaudRate baudRate) | 47 | void SerialPort::SetBaudRate(BaudRate baudRate) |
| @@ -89,18 +91,19 @@ namespace CppLinuxSerial { | @@ -89,18 +91,19 @@ namespace CppLinuxSerial { | ||
| 89 | 91 | ||
| 90 | // O_RDONLY for read-only, O_WRONLY for write only, O_RDWR for both read/write access | 92 | // O_RDONLY for read-only, O_WRONLY for write only, O_RDWR for both read/write access |
| 91 | // 3rd, optional parameter is mode_t mode | 93 | // 3rd, optional parameter is mode_t mode |
| 92 | - this->fileDesc = open(device_.c_str(), O_RDWR); | 94 | + fileDesc_ = open(device_.c_str(), O_RDWR); |
| 93 | 95 | ||
| 94 | // Check status | 96 | // Check status |
| 95 | - if (this->fileDesc == -1) | ||
| 96 | - { | 97 | + if(fileDesc_ == -1) { |
| 97 | // Could not open COM port | 98 | // Could not open COM port |
| 98 | //this->sp->PrintError(SmartPrint::Ss() << "Unable to open " << this->filePath << " - " << strerror(errno)); | 99 | //this->sp->PrintError(SmartPrint::Ss() << "Unable to open " << this->filePath << " - " << strerror(errno)); |
| 99 | //return false; | 100 | //return false; |
| 100 | 101 | ||
| 101 | - throw std::system_error(EFAULT, std::system_category()); | 102 | + throw std::runtime_error("Could not open device " + device_ + ". Is the device name correct and do you have read/write permission?"); |
| 102 | } | 103 | } |
| 103 | 104 | ||
| 105 | + ConfigureDeviceAsSerialPort(); | ||
| 106 | + | ||
| 104 | std::cout << "COM port opened successfully." << std::endl; | 107 | std::cout << "COM port opened successfully." << std::endl; |
| 105 | 108 | ||
| 106 | // If code reaches here, open and config must of been successful | 109 | // If code reaches here, open and config must of been successful |
| @@ -231,7 +234,7 @@ namespace CppLinuxSerial { | @@ -231,7 +234,7 @@ namespace CppLinuxSerial { | ||
| 231 | 234 | ||
| 232 | void SerialPort::Write(std::string* str) | 235 | void SerialPort::Write(std::string* str) |
| 233 | { | 236 | { |
| 234 | - if(this->fileDesc == 0) | 237 | + if(this->fileDesc_ == 0) |
| 235 | { | 238 | { |
| 236 | //this->sp->PrintError(SmartPrint::Ss() << ); | 239 | //this->sp->PrintError(SmartPrint::Ss() << ); |
| 237 | //return false; | 240 | //return false; |
| @@ -239,7 +242,7 @@ namespace CppLinuxSerial { | @@ -239,7 +242,7 @@ namespace CppLinuxSerial { | ||
| 239 | throw std::runtime_error("SendMsg called but file descriptor (fileDesc) was 0, indicating file has not been opened."); | 242 | throw std::runtime_error("SendMsg called but file descriptor (fileDesc) was 0, indicating file has not been opened."); |
| 240 | } | 243 | } |
| 241 | 244 | ||
| 242 | - int writeResult = write(this->fileDesc, str->c_str(), str->size()); | 245 | + int writeResult = write(this->fileDesc_, str->c_str(), str->size()); |
| 243 | 246 | ||
| 244 | // Check status | 247 | // Check status |
| 245 | if (writeResult == -1) | 248 | if (writeResult == -1) |
| @@ -256,7 +259,7 @@ namespace CppLinuxSerial { | @@ -256,7 +259,7 @@ namespace CppLinuxSerial { | ||
| 256 | 259 | ||
| 257 | void SerialPort::Read(std::string* str) | 260 | void SerialPort::Read(std::string* str) |
| 258 | { | 261 | { |
| 259 | - if(this->fileDesc == 0) | 262 | + if(this->fileDesc_ == 0) |
| 260 | { | 263 | { |
| 261 | //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); | 264 | //this->sp->PrintError(SmartPrint::Ss() << "Read() was called but file descriptor (fileDesc) was 0, indicating file has not been opened."); |
| 262 | //return false; | 265 | //return false; |
| @@ -268,7 +271,7 @@ namespace CppLinuxSerial { | @@ -268,7 +271,7 @@ namespace CppLinuxSerial { | ||
| 268 | memset (&buf, '\0', sizeof buf); | 271 | memset (&buf, '\0', sizeof buf); |
| 269 | 272 | ||
| 270 | // Read from file | 273 | // Read from file |
| 271 | - int n = read(this->fileDesc, &buf, sizeof(buf)); | 274 | + int n = read(this->fileDesc_, &buf, sizeof(buf)); |
| 272 | 275 | ||
| 273 | // Error Handling | 276 | // Error Handling |
| 274 | if(n < 0) | 277 | if(n < 0) |
| @@ -295,11 +298,14 @@ namespace CppLinuxSerial { | @@ -295,11 +298,14 @@ namespace CppLinuxSerial { | ||
| 295 | 298 | ||
| 296 | termios SerialPort::GetTermios() | 299 | termios SerialPort::GetTermios() |
| 297 | { | 300 | { |
| 301 | + if(fileDesc_ == -1) | ||
| 302 | + throw std::runtime_error("GetTermios() called but file descriptor was not valid."); | ||
| 303 | + | ||
| 298 | struct termios tty; | 304 | struct termios tty; |
| 299 | memset(&tty, 0, sizeof(tty)); | 305 | memset(&tty, 0, sizeof(tty)); |
| 300 | 306 | ||
| 301 | // Get current settings (will be stored in termios structure) | 307 | // Get current settings (will be stored in termios structure) |
| 302 | - if(tcgetattr(this->fileDesc, &tty) != 0) | 308 | + if(tcgetattr(fileDesc_, &tty) != 0) |
| 303 | { | 309 | { |
| 304 | // Error occurred | 310 | // Error occurred |
| 305 | std::cout << "Could not get terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; | 311 | std::cout << "Could not get terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; |
| @@ -313,9 +319,9 @@ namespace CppLinuxSerial { | @@ -313,9 +319,9 @@ namespace CppLinuxSerial { | ||
| 313 | void SerialPort::SetTermios(termios myTermios) | 319 | void SerialPort::SetTermios(termios myTermios) |
| 314 | { | 320 | { |
| 315 | // Flush port, then apply attributes | 321 | // Flush port, then apply attributes |
| 316 | - tcflush(this->fileDesc, TCIFLUSH); | 322 | + tcflush(this->fileDesc_, TCIFLUSH); |
| 317 | 323 | ||
| 318 | - if(tcsetattr(this->fileDesc, TCSANOW, &myTermios) != 0) | 324 | + if(tcsetattr(this->fileDesc_, TCSANOW, &myTermios) != 0) |
| 319 | { | 325 | { |
| 320 | // Error occurred | 326 | // Error occurred |
| 321 | std::cout << "Could not apply terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; | 327 | std::cout << "Could not apply terminal attributes for \"" << device_ << "\" - " << strerror(errno) << std::endl; |
| @@ -326,5 +332,17 @@ namespace CppLinuxSerial { | @@ -326,5 +332,17 @@ namespace CppLinuxSerial { | ||
| 326 | // Successful! | 332 | // Successful! |
| 327 | } | 333 | } |
| 328 | 334 | ||
| 335 | + void SerialPort::Close() { | ||
| 336 | + if(fileDesc_ != -1) { | ||
| 337 | + auto retVal = close(fileDesc_); | ||
| 338 | + if(retVal != 0) | ||
| 339 | + throw std::runtime_error("Tried to close serial port " + device_ + ", but close() failed."); | ||
| 340 | + | ||
| 341 | + fileDesc_ = -1; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + state_ = State::CLOSED; | ||
| 345 | + } | ||
| 346 | + | ||
| 329 | } // namespace CppLinuxSerial | 347 | } // namespace CppLinuxSerial |
| 330 | } // namespace mn | 348 | } // namespace mn |
test/unit/BasicTests.cpp
| 1 | +/// | ||
| 2 | +/// \file BasicTests.cpp | ||
| 3 | +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja) | ||
| 4 | +/// \created 2017-11-24 | ||
| 5 | +/// \last-modified 2017-11-24 | ||
| 6 | +/// \brief Basic tests for the SerialPort class. | ||
| 7 | +/// \details | ||
| 8 | +/// See README.rst in repo root dir for more info. | ||
| 9 | + | ||
| 10 | +// System includes | ||
| 1 | #include "gtest/gtest.h" | 11 | #include "gtest/gtest.h" |
| 2 | 12 | ||
| 13 | +// 3rd party includes | ||
| 3 | #include "CppLinuxSerial/SerialPort.hpp" | 14 | #include "CppLinuxSerial/SerialPort.hpp" |
| 4 | 15 | ||
| 16 | +// User includes | ||
| 17 | +#include "TestUtil.hpp" | ||
| 18 | + | ||
| 5 | using namespace mn::CppLinuxSerial; | 19 | using namespace mn::CppLinuxSerial; |
| 6 | 20 | ||
| 7 | namespace { | 21 | namespace { |
| @@ -9,11 +23,29 @@ namespace { | @@ -9,11 +23,29 @@ namespace { | ||
| 9 | class BasicTests : public ::testing::Test { | 23 | class BasicTests : public ::testing::Test { |
| 10 | protected: | 24 | protected: |
| 11 | 25 | ||
| 26 | + static void SetUpTestCase() { | ||
| 27 | + GetTestUtil().CreateVirtualSerialPortPair(); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + static void TearDownTestCase() { | ||
| 31 | + std::cout << "Destroying virtual serial ports..." << std::endl; | ||
| 32 | + GetTestUtil().CloseSerialPorts(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + static TestUtil& GetTestUtil() { | ||
| 36 | + static TestUtil testUtil; | ||
| 37 | + return testUtil; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + | ||
| 12 | BasicTests() { | 41 | BasicTests() { |
| 13 | } | 42 | } |
| 14 | 43 | ||
| 15 | virtual ~BasicTests() { | 44 | virtual ~BasicTests() { |
| 16 | } | 45 | } |
| 46 | + | ||
| 47 | + std::string device0_ = "/dev/ttyS10"; | ||
| 48 | + std::string device1_ = "/dev/ttyS11"; | ||
| 17 | }; | 49 | }; |
| 18 | 50 | ||
| 19 | TEST_F(BasicTests, CanBeConstructed) { | 51 | TEST_F(BasicTests, CanBeConstructed) { |
| @@ -22,9 +54,17 @@ namespace { | @@ -22,9 +54,17 @@ namespace { | ||
| 22 | } | 54 | } |
| 23 | 55 | ||
| 24 | TEST_F(BasicTests, CanOpen) { | 56 | TEST_F(BasicTests, CanOpen) { |
| 25 | - SerialPort serialPort; | ||
| 26 | - serialPort.Open(); | ||
| 27 | - EXPECT_EQ(true, true); | 57 | + SerialPort serialPort0(device0_, BaudRate::b57600); |
| 58 | + serialPort0.Open(); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + TEST_F(BasicTests, ReadWrite) { | ||
| 62 | + SerialPort serialPort0(device0_, BaudRate::b57600); | ||
| 63 | + serialPort0.Open(); | ||
| 64 | + | ||
| 65 | + SerialPort serialPort1(device1_, BaudRate::b57600); | ||
| 66 | + serialPort1.Open(); | ||
| 67 | + | ||
| 28 | } | 68 | } |
| 29 | 69 | ||
| 30 | } // namespace | 70 | } // namespace |
| 31 | \ No newline at end of file | 71 | \ No newline at end of file |
test/unit/TestUtil.hpp
0 → 100644
| 1 | +/// | ||
| 2 | +/// \file TestUtil.hpp | ||
| 3 | +/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja) | ||
| 4 | +/// \created 2017-11-24 | ||
| 5 | +/// \last-modified 2017-11-24 | ||
| 6 | +/// \brief Contains utility methods to help with testing. | ||
| 7 | +/// \details | ||
| 8 | +/// See README.rst in repo root dir for more info. | ||
| 9 | + | ||
| 10 | +#ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_ | ||
| 11 | +#define MN_CPP_LINUX_SERIAL_TEST_UTIL_H_ | ||
| 12 | + | ||
| 13 | +// System includes | ||
| 14 | +#include <string> | ||
| 15 | +#include <array> | ||
| 16 | +#include <memory> | ||
| 17 | +#include <iostream> | ||
| 18 | +#include <thread> | ||
| 19 | +#include <chrono> | ||
| 20 | + | ||
| 21 | +// 3rd party includes | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +using namespace std::literals; | ||
| 25 | + | ||
| 26 | +#define READ 0 | ||
| 27 | +#define WRITE 1 | ||
| 28 | +FILE * popen2(std::string command, std::string type, int & pid) | ||
| 29 | +{ | ||
| 30 | + pid_t child_pid; | ||
| 31 | + int fd[2]; | ||
| 32 | + pipe(fd); | ||
| 33 | + | ||
| 34 | + if((child_pid = fork()) == -1) | ||
| 35 | + { | ||
| 36 | + perror("fork"); | ||
| 37 | + exit(1); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + /* child process */ | ||
| 41 | + if (child_pid == 0) | ||
| 42 | + { | ||
| 43 | + if (type == "r") | ||
| 44 | + { | ||
| 45 | + close(fd[READ]); //Close the READ end of the pipe since the child's fd is write-only | ||
| 46 | + dup2(fd[WRITE], 1); //Redirect stdout to pipe | ||
| 47 | + } | ||
| 48 | + else | ||
| 49 | + { | ||
| 50 | + close(fd[WRITE]); //Close the WRITE end of the pipe since the child's fd is read-only | ||
| 51 | + dup2(fd[READ], 0); //Redirect stdin to pipe | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + setpgid(child_pid, child_pid); //Needed so negative PIDs can kill children of /bin/sh | ||
| 55 | + execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL); | ||
| 56 | + exit(0); | ||
| 57 | + } | ||
| 58 | + else | ||
| 59 | + { | ||
| 60 | + if (type == "r") | ||
| 61 | + { | ||
| 62 | + close(fd[WRITE]); //Close the WRITE end of the pipe since parent's fd is read-only | ||
| 63 | + } | ||
| 64 | + else | ||
| 65 | + { | ||
| 66 | + close(fd[READ]); //Close the READ end of the pipe since parent's fd is write-only | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + pid = child_pid; | ||
| 71 | + | ||
| 72 | + if (type == "r") | ||
| 73 | + { | ||
| 74 | + return fdopen(fd[READ], "r"); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + return fdopen(fd[WRITE], "w"); | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +int pclose2(FILE * fp, pid_t pid) | ||
| 81 | +{ | ||
| 82 | + int stat; | ||
| 83 | + | ||
| 84 | + fclose(fp); | ||
| 85 | + while (waitpid(pid, &stat, 0) == -1) | ||
| 86 | + { | ||
| 87 | + if (errno != EINTR) | ||
| 88 | + { | ||
| 89 | + stat = -1; | ||
| 90 | + break; | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + return stat; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +struct ProcessInfo { | ||
| 98 | + FILE* fp; | ||
| 99 | + pid_t pid; | ||
| 100 | +}; | ||
| 101 | + | ||
| 102 | + | ||
| 103 | +namespace mn { | ||
| 104 | + namespace CppLinuxSerial { | ||
| 105 | + | ||
| 106 | + class TestUtil { | ||
| 107 | + | ||
| 108 | + public: | ||
| 109 | + /// \brief Executes a command on the Linux command-line. | ||
| 110 | + /// \details Blocks until command is complete. | ||
| 111 | + /// \throws std::runtime_error is popen() fails. | ||
| 112 | + static std::string Exec(const std::string &cmd) { | ||
| 113 | + std::array<char, 128> buffer; | ||
| 114 | + std::string result; | ||
| 115 | + std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose); | ||
| 116 | + if (!pipe) throw std::runtime_error("popen() failed!"); | ||
| 117 | + | ||
| 118 | + while (!feof(pipe.get())) { | ||
| 119 | + if (fgets(buffer.data(), 128, pipe.get()) != nullptr) | ||
| 120 | + result += buffer.data(); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + return result; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + void StartProcess(const std::string &cmd) { | ||
| 127 | + std::array<char, 128> buffer; | ||
| 128 | + std::string result; | ||
| 129 | + int pid; | ||
| 130 | + FILE * fp = popen2(cmd, "r", pid); | ||
| 131 | + ProcessInfo processInfo; | ||
| 132 | + processInfo.fp = fp; | ||
| 133 | + processInfo.pid = pid; | ||
| 134 | + processes_.push_back(processInfo); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + void CreateVirtualSerialPortPair() { | ||
| 138 | + std::cout << "Creating virtual serial port pair..." << std::endl; | ||
| 139 | + StartProcess("sudo socat -d -d pty,raw,echo=0,link=/dev/ttyS10 pty,raw,echo=0,link=/dev/ttyS11"); | ||
| 140 | + std::this_thread::sleep_for(1s); | ||
| 141 | + StartProcess("sudo chmod a+rw /dev/ttyS10"); | ||
| 142 | + StartProcess("sudo chmod a+rw /dev/ttyS11"); | ||
| 143 | + std::this_thread::sleep_for(1s); | ||
| 144 | + std::cout << "Finished creating virtual serial port pair." << std::endl; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + void CloseSerialPorts() { | ||
| 148 | + for(const auto& filePointer : processes_) { | ||
| 149 | + kill(filePointer.pid, SIGKILL); | ||
| 150 | + pclose2(filePointer.fp, filePointer.pid); | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + std::vector<ProcessInfo> processes_; | ||
| 155 | + | ||
| 156 | + }; | ||
| 157 | + } // namespace CppLinuxSerial | ||
| 158 | +} // namespace mn | ||
| 159 | + | ||
| 160 | +#endif // #ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_ |