Commit 2e35cebd53a35d3059c893d6d2a35b1e5b29daa4

Authored by Geoffrey Hunter
1 parent 9bfbe366

Working on basic read/write unit test using two virtual serial ports.

.gitignore
1 -build/  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +build/
  3 +cmake-build-debug/
  4 +.idea/workspace.xml
3 \ No newline at end of file 5 \ No newline at end of file
.idea/CppLinuxSerial.iml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<module classpath="CMake" type="CPP_MODULE" version="4" />
0 \ No newline at end of file 3 \ No newline at end of file
.idea/misc.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
  4 +</project>
0 \ No newline at end of file 5 \ No newline at end of file
.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
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="VcsDirectoryMappings">
  4 + <mapping directory="$PROJECT_DIR$" vcs="Git" />
  5 + </component>
  6 +</project>
0 \ No newline at end of file 7 \ No newline at end of file
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_