tests: investigating catch2
[m6w6/libmemcached] / testing / lib / Server.cpp
1 #include "Server.hpp"
2 #include "WaitForExec.hpp"
3 #include "WaitForConn.hpp"
4
5 #include <netinet/in.h>
6 #include <sys/poll.h>
7 #include <sys/socket.h>
8 #include <sys/un.h>
9 #include <sys/wait.h>
10 #include <unistd.h>
11
12 #include <iostream>
13 #include <tuple>
14
15 using namespace std;
16
17 [[nodiscard]]
18 auto Server::createArgv() {
19 auto i = 0, port = -1, socket = -1;
20 auto arr = new char *[str_args.size() + dyn_args.size()*2 + 2] {
21 strdup(binary.c_str())
22 };
23
24 for (auto &arg : str_args) {
25 arr[++i] = strdup(arg.c_str());
26 if (arg == "-p") {
27 port = i + 1;
28 } else if (arg == "-s") {
29 socket = i + 1;
30 }
31 }
32 for (auto &arg : dyn_args) {
33 arr[++i] = strdup(arg.first.c_str());
34 arr[++i] = strdup(arg.second(arg.first).c_str());
35 if (arg.first == "-p") {
36 port = i;
37 } else if (arg.first == "-s") {
38 socket = i;
39 }
40
41 }
42 arr[i+1] = nullptr;
43
44 if (socket > -1) {
45 socket_or_port = arr[socket];
46 } else if (port > -1) {
47 socket_or_port = stoi(arr[port]);
48 } else {
49 socket_or_port = 11211;
50 }
51
52 return arr;
53 }
54
55 [[nodiscard]]
56 optional<WaitForConn::conn_t> Server::createSocket() {
57 sockaddr_storage addr;
58 unsigned size = 0;
59 int sock;
60
61 if (holds_alternative<string>(socket_or_port)) {
62 const auto path = get<string>(socket_or_port);
63 const auto safe = path.c_str();
64 const auto zlen = path.length() + 1;
65 const auto ulen = sizeof(sockaddr_un) - sizeof(sa_family_t);
66
67 if (zlen >= ulen) {
68 cerr << "Server::isListening socket(): path too long '" << path << "'\n";
69 return {};
70 }
71
72 if (0 > (sock = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
73 perror("Server::isListening socket()");
74 return {};
75 }
76
77 auto sa = reinterpret_cast<sockaddr_un *>(&addr);
78 sa->sun_family = AF_UNIX;
79 strncpy(sa->sun_path, safe, zlen);
80
81 size = sizeof(*sa);
82 } else {
83 if (0 > (sock = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
84 perror("Server::isListening socket()");
85 return {};
86 }
87
88 const auto port = get<int>(socket_or_port);
89 auto sa = reinterpret_cast<struct sockaddr_in *>(&addr);
90 sa->sin_family = AF_INET;
91 sa->sin_port = htons(static_cast<unsigned short>(port)),
92 sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
93
94 size = sizeof(*sa);
95 }
96
97 return optional<WaitForConn::conn_t>{make_tuple(sock, addr, size)};
98 }
99
100 optional<pid_t> Server::start() {
101 if (pid) {
102 return pid;
103 }
104
105 WaitForExec wait_for_exec;
106
107 switch (pid = fork()) {
108 case 0:
109 execvp(binary.c_str(), createArgv());
110 [[fallthrough]];
111 case -1:
112 perror("Server::start fork() & exec()");
113 return {};
114
115 default:
116 if (!wait_for_exec()) {
117 cerr << "Server::start exec(): incomplete\n";
118 }
119 return pid;
120 }
121 }
122
123 bool Server::isListening(int max_timeout) {
124 auto conn = createSocket();
125
126 if (!conn) {
127 return false;
128 }
129
130 WaitForConn wait_for_conn{
131 {conn.value()},
132 Poll{POLLOUT, 2, max_timeout}
133 };
134 return wait_for_conn();
135 }
136
137 bool Server::stop() {
138 if (!pid) {
139 return true;
140 }
141 if (signalled[SIGTERM]) {
142 return signal(SIGKILL);
143 } else {
144 return signal(SIGTERM);
145 }
146 }
147
148 bool Server::signal(int signo) {
149 if (!pid) {
150 return false;
151 }
152 signalled[signo] += 1;
153 return 0 <= kill(pid, signo);
154 }
155
156 bool Server::check() {
157 return signal(0);
158 }
159
160 bool Server::wait(int flags) {
161 if (pid && pid == waitpid(pid, &status, flags)) {
162 pid = 0;
163 return true;
164 }
165 return false;
166 }
167
168 bool Server::tryWait() {
169 return wait(WNOHANG);
170 }