TestUtil.hpp
4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
///
/// \file TestUtil.hpp
/// \author Geoffrey Hunter <gbmhunter@gmail.com> (www.mbedded.ninja)
/// \created 2017-11-24
/// \last-modified 2017-11-24
/// \brief Contains utility methods to help with testing.
/// \details
/// See README.rst in repo root dir for more info.
#ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_
#define MN_CPP_LINUX_SERIAL_TEST_UTIL_H_
// System includes
#include <string>
#include <array>
#include <memory>
#include <iostream>
#include <thread>
#include <chrono>
// 3rd party includes
using namespace std::literals;
#define READ 0
#define WRITE 1
FILE * popen2(std::string command, std::string type, int & pid)
{
pid_t child_pid;
int fd[2];
pipe(fd);
if((child_pid = fork()) == -1)
{
perror("fork");
exit(1);
}
/* child process */
if (child_pid == 0)
{
if (type == "r")
{
close(fd[READ]); //Close the READ end of the pipe since the child's fd is write-only
dup2(fd[WRITE], 1); //Redirect stdout to pipe
}
else
{
close(fd[WRITE]); //Close the WRITE end of the pipe since the child's fd is read-only
dup2(fd[READ], 0); //Redirect stdin to pipe
}
setpgid(child_pid, child_pid); //Needed so negative PIDs can kill children of /bin/sh
execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL);
exit(0);
}
else
{
if (type == "r")
{
close(fd[WRITE]); //Close the WRITE end of the pipe since parent's fd is read-only
}
else
{
close(fd[READ]); //Close the READ end of the pipe since parent's fd is write-only
}
}
pid = child_pid;
if (type == "r")
{
return fdopen(fd[READ], "r");
}
return fdopen(fd[WRITE], "w");
}
int pclose2(FILE * fp, pid_t pid)
{
int stat;
fclose(fp);
while (waitpid(pid, &stat, 0) == -1)
{
if (errno != EINTR)
{
stat = -1;
break;
}
}
return stat;
}
struct ProcessInfo {
FILE* fp;
pid_t pid;
};
namespace mn {
namespace CppLinuxSerial {
class TestUtil {
public:
/// \brief Executes a command on the Linux command-line.
/// \details Blocks until command is complete.
/// \throws std::runtime_error is popen() fails.
static std::string Exec(const std::string &cmd) {
std::array<char, 128> buffer;
std::string result;
std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
result += buffer.data();
}
return result;
}
void StartProcess(const std::string &cmd) {
std::array<char, 128> buffer;
std::string result;
int pid;
FILE * fp = popen2(cmd, "r", pid);
ProcessInfo processInfo;
processInfo.fp = fp;
processInfo.pid = pid;
processes_.push_back(processInfo);
}
void CreateVirtualSerialPortPair() {
std::cout << "Creating virtual serial port pair..." << std::endl;
StartProcess("sudo socat -d -d pty,raw,echo=0,link=/dev/ttyS10 pty,raw,echo=0,link=/dev/ttyS11");
std::this_thread::sleep_for(1s);
StartProcess("sudo chmod a+rw /dev/ttyS10");
StartProcess("sudo chmod a+rw /dev/ttyS11");
std::this_thread::sleep_for(1s);
std::cout << "Finished creating virtual serial port pair." << std::endl;
}
void CloseSerialPorts() {
for(const auto& filePointer : processes_) {
kill(filePointer.pid, SIGKILL);
pclose2(filePointer.fp, filePointer.pid);
}
}
std::vector<ProcessInfo> processes_;
};
} // namespace CppLinuxSerial
} // namespace mn
#endif // #ifndef MN_CPP_LINUX_SERIAL_TEST_UTIL_H_