Line data Source code
1 : // Copyright (c) 2016-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/cluck
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "catch_main.h"
22 :
23 :
24 :
25 : // daemon
26 : //
27 : #include <daemon/computer.h>
28 :
29 :
30 : // cluck
31 : //
32 : //#include <cluck/cluck.h>
33 : #include <cluck/exception.h>
34 : //#include <cluck/names.h>
35 : //#include <cluck/version.h>
36 :
37 :
38 : // eventdispatcher
39 : //
40 : //#include <eventdispatcher/communicator.h>
41 : //#include <eventdispatcher/dispatcher.h>
42 : //#include <eventdispatcher/names.h>
43 : //#include <eventdispatcher/tcp_client_permanent_message_connection.h>
44 : //
45 : //#include <eventdispatcher/reporter/executor.h>
46 : //#include <eventdispatcher/reporter/lexer.h>
47 : //#include <eventdispatcher/reporter/parser.h>
48 : //#include <eventdispatcher/reporter/state.h>
49 :
50 :
51 : // cppthread
52 : //
53 : #include <cppthread/thread.h>
54 :
55 :
56 : // snapdev
57 : //
58 : #include <snapdev/enum_class_math.h>
59 : #include <snapdev/escape_special_regex_characters.h>
60 :
61 :
62 : // C++
63 : //
64 : #include <regex>
65 :
66 :
67 : // last include
68 : //
69 : #include <snapdev/poison.h>
70 :
71 :
72 :
73 : namespace
74 : {
75 :
76 :
77 :
78 15 : addr::addr get_random_address()
79 : {
80 15 : addr::addr a;
81 15 : sockaddr_in ip = {
82 : .sin_family = AF_INET,
83 15 : .sin_port = htons(20002),
84 : .sin_addr = {
85 : .s_addr = 0,
86 : },
87 : .sin_zero = {},
88 15 : };
89 15 : SNAP_CATCH2_NAMESPACE::random(ip.sin_addr.s_addr);
90 15 : a.set_ipv4(ip);
91 30 : return a;
92 0 : }
93 :
94 :
95 :
96 : } // no name namespace
97 :
98 :
99 :
100 2 : CATCH_TEST_CASE("daemon_computer", "[cluckd][computer][daemon]")
101 : {
102 4 : CATCH_START_SECTION("daemon_computer: verify defaults")
103 : {
104 1 : cluck_daemon::computer c;
105 :
106 1 : CATCH_REQUIRE_FALSE(c.is_self());
107 1 : CATCH_REQUIRE(c.get_connected());
108 1 : CATCH_REQUIRE(c.get_priority() == cluck_daemon::computer::PRIORITY_UNDEFINED);
109 1 : CATCH_REQUIRE(c.get_start_time() == snapdev::timespec_ex());
110 1 : CATCH_REQUIRE(c.get_name() == std::string());
111 1 : CATCH_REQUIRE(c.get_ip_address() == addr::addr());
112 :
113 : // without a priority, this one fails
114 : //
115 1 : CATCH_REQUIRE_THROWS_MATCHES(
116 : c.get_id()
117 : , cluck::invalid_parameter
118 : , Catch::Matchers::ExceptionMessage("cluck_exception: computer::get_id() can't be called when the priority is not defined."));
119 1 : }
120 3 : CATCH_END_SECTION()
121 :
122 4 : CATCH_START_SECTION("daemon_computer: verify self defaults")
123 : {
124 : // the LEADER priority cannot get saved inside the computer object
125 : //
126 16 : for(cluck_daemon::computer::priority_t p(cluck_daemon::computer::PRIORITY_USER_MIN);
127 16 : p <= cluck_daemon::computer::PRIORITY_MAX;
128 : ++p)
129 : {
130 15 : CATCH_REQUIRE(p != cluck_daemon::computer::PRIORITY_LEADER);
131 :
132 15 : std::string n;
133 : do
134 : {
135 16 : n = SNAP_CATCH2_NAMESPACE::random_string(1, 15);
136 : }
137 16 : while(std::strchr(n.c_str(), '|') != nullptr);
138 15 : addr::addr a(get_random_address());
139 30 : cluck_daemon::computer c(n, p, a);
140 :
141 15 : CATCH_REQUIRE(c.is_self());
142 15 : CATCH_REQUIRE(c.get_connected());
143 15 : CATCH_REQUIRE(c.get_priority() == p);
144 15 : CATCH_REQUIRE(c.get_start_time() == snapdev::timespec_ex());
145 15 : CATCH_REQUIRE(c.get_name() == n);
146 15 : CATCH_REQUIRE(c.get_ip_address() == a);
147 :
148 : // with a priority, this one works
149 : //
150 15 : std::string const id(c.get_id());
151 15 : CATCH_REQUIRE(c.get_id() == id); // ID does not change after first call
152 :
153 : // the ID is a string defined as:
154 : // <priority> | <random ID> | <IP> | <pid> | <name>
155 : //
156 : // we do not have access to the <random ID> part so we use a
157 : // regex to make sure that the ID is defined as expected
158 : //
159 15 : std::string expr;
160 15 : if(p < 10)
161 : {
162 9 : expr += '0';
163 : }
164 15 : expr += std::to_string(p);
165 15 : expr += "\\|[0-9]+\\|";
166 15 : expr += snapdev::escape_special_regex_characters(a.to_ipv4or6_string(addr::STRING_IP_ADDRESS | addr::STRING_IP_BRACKET_ADDRESS));
167 15 : expr += "\\|";
168 15 : expr += std::to_string(getpid());
169 15 : expr += "\\|";
170 15 : expr += snapdev::escape_special_regex_characters(n);
171 15 : std::regex const compiled_regex(expr);
172 15 : CATCH_REQUIRE(std::regex_match(id, compiled_regex));
173 :
174 15 : cluck_daemon::computer copy;
175 15 : CATCH_REQUIRE_FALSE(copy.is_self());
176 15 : CATCH_REQUIRE(copy.get_connected());
177 15 : CATCH_REQUIRE(copy.get_priority() == cluck_daemon::computer::PRIORITY_UNDEFINED);
178 15 : CATCH_REQUIRE(copy.get_start_time() == snapdev::timespec_ex());
179 15 : CATCH_REQUIRE(copy.get_name() == std::string());
180 15 : CATCH_REQUIRE(copy.get_ip_address() == addr::addr());
181 :
182 15 : CATCH_REQUIRE(copy.set_id(id));
183 15 : CATCH_REQUIRE(copy.get_id() == id);
184 :
185 15 : CATCH_REQUIRE_FALSE(copy.is_self());
186 15 : CATCH_REQUIRE(copy.get_connected());
187 15 : CATCH_REQUIRE(copy.get_priority() == p);
188 15 : CATCH_REQUIRE(copy.get_start_time() == snapdev::timespec_ex());
189 15 : CATCH_REQUIRE(copy.get_name() == n);
190 15 : CATCH_REQUIRE(copy.get_ip_address() == a);
191 :
192 : // trying to set the ID again fails
193 : //
194 15 : CATCH_REQUIRE_THROWS_MATCHES(
195 : copy.set_id(id)
196 : , cluck::logic_error
197 : , Catch::Matchers::ExceptionMessage("logic_error: computer::set_id() cannot be called more than once."));
198 15 : }
199 : }
200 3 : CATCH_END_SECTION()
201 2 : }
202 :
203 :
204 9 : CATCH_TEST_CASE("daemon_computer_errors", "[cluckd][computer][daemon][error]")
205 : {
206 11 : CATCH_START_SECTION("daemon_computer_errors: empty name")
207 : {
208 7 : CATCH_REQUIRE_THROWS_MATCHES(
209 : cluck_daemon::computer("", 5, addr::addr())
210 : , cluck::invalid_parameter
211 : , Catch::Matchers::ExceptionMessage("cluck_exception: the computer name cannot be an empty string."));
212 : }
213 10 : CATCH_END_SECTION()
214 :
215 11 : CATCH_START_SECTION("daemon_computer_errors: invalid character in name")
216 : {
217 : // the | character is an issue in the serializer
218 : //
219 7 : CATCH_REQUIRE_THROWS_MATCHES(
220 : cluck_daemon::computer("|pipe-not-allowed|", 5, addr::addr())
221 : , cluck::invalid_parameter
222 : , Catch::Matchers::ExceptionMessage("cluck_exception: a computer name cannot include the '|' or null characters."));
223 :
224 : // the '\0' is not allowed in the name
225 : //
226 2 : std::string n("start");
227 1 : n += '\0';
228 1 : n += "end";
229 3 : CATCH_REQUIRE_THROWS_MATCHES(
230 : cluck_daemon::computer(n, 5, addr::addr())
231 : , cluck::invalid_parameter
232 : , Catch::Matchers::ExceptionMessage("cluck_exception: a computer name cannot include the '|' or null characters."));
233 1 : }
234 10 : CATCH_END_SECTION()
235 :
236 11 : CATCH_START_SECTION("daemon_computer_errors: serialized ID must be 5 parts")
237 : {
238 1 : cluck_daemon::computer c;
239 1 : CATCH_REQUIRE_FALSE(c.set_id("need|5|parts"));
240 1 : }
241 10 : CATCH_END_SECTION()
242 :
243 11 : CATCH_START_SECTION("daemon_computer_errors: invalid priority with constructor")
244 : {
245 : // too small
246 7 : CATCH_REQUIRE_THROWS_MATCHES(
247 : cluck_daemon::computer("test", cluck_daemon::computer::PRIORITY_USER_MIN - 1, addr::addr())
248 : , cluck::invalid_parameter
249 : , Catch::Matchers::ExceptionMessage(
250 : "cluck_exception: priority is limited to a number between "
251 : + std::to_string(static_cast<int>(cluck_daemon::computer::PRIORITY_USER_MIN))
252 : + " and "
253 : + std::to_string(static_cast<int>(cluck_daemon::computer::PRIORITY_MAX))
254 : + " inclusive."));
255 :
256 : // too large
257 7 : CATCH_REQUIRE_THROWS_MATCHES(
258 : cluck_daemon::computer("test", cluck_daemon::computer::PRIORITY_MAX + 1, addr::addr())
259 : , cluck::invalid_parameter
260 : , Catch::Matchers::ExceptionMessage(
261 : "cluck_exception: priority is limited to a number between "
262 : + std::to_string(static_cast<int>(cluck_daemon::computer::PRIORITY_USER_MIN))
263 : + " and "
264 : + std::to_string(static_cast<int>(cluck_daemon::computer::PRIORITY_MAX))
265 : + " inclusive."));
266 : }
267 10 : CATCH_END_SECTION()
268 :
269 11 : CATCH_START_SECTION("daemon_computer_errors: invalid priority in id string")
270 : {
271 : // not a number
272 : {
273 1 : cluck_daemon::computer c;
274 1 : CATCH_REQUIRE_FALSE(c.set_id("prio|123|127.0.0.1|5501|name"));
275 1 : }
276 :
277 : // too small
278 : {
279 1 : cluck_daemon::computer c;
280 1 : std::string too_small(std::to_string(cluck_daemon::computer::PRIORITY_USER_MIN - 1));
281 1 : CATCH_REQUIRE_FALSE(c.set_id(too_small + "|123|127.0.0.1|5501|name"));
282 1 : }
283 :
284 : // too large
285 : {
286 1 : cluck_daemon::computer c;
287 1 : std::string too_large(std::to_string(cluck_daemon::computer::PRIORITY_MAX + 1));
288 1 : CATCH_REQUIRE_FALSE(c.set_id(too_large + "|123|127.0.0.1|5501|name"));
289 1 : }
290 : }
291 10 : CATCH_END_SECTION()
292 :
293 11 : CATCH_START_SECTION("daemon_computer_errors: invalid random number in id string")
294 : {
295 : // not a number
296 : {
297 1 : cluck_daemon::computer c;
298 1 : CATCH_REQUIRE_FALSE(c.set_id("10|random|127.0.0.1|5501|name"));
299 1 : }
300 : }
301 10 : CATCH_END_SECTION()
302 :
303 11 : CATCH_START_SECTION("daemon_computer_errors: invalid IP address in id string")
304 : {
305 : // empty
306 : {
307 1 : cluck_daemon::computer c;
308 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001||5501|name"));
309 1 : }
310 :
311 : // not an IP
312 : {
313 1 : cluck_daemon::computer c;
314 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|not an IP|5501|name"));
315 1 : }
316 :
317 : // "default"
318 : {
319 1 : cluck_daemon::computer c;
320 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|0.0.0.0|5501|name"));
321 :
322 : // this is a trick that works at the moment; we set a valid
323 : // priority, so next we can have an invalid address that
324 : // remains... because the set_id() still makes updates to
325 : // the object even if it generates an error
326 : //
327 1 : CATCH_REQUIRE_THROWS_MATCHES(
328 : c.get_id()
329 : , cluck::invalid_parameter
330 : , Catch::Matchers::ExceptionMessage("cluck_exception: computer::get_id() can't be called when the address is the default address."));
331 1 : }
332 : }
333 10 : CATCH_END_SECTION()
334 :
335 11 : CATCH_START_SECTION("daemon_computer_errors: invalid PID")
336 : {
337 : // empty
338 : {
339 1 : cluck_daemon::computer c;
340 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|127.0.0.1||name"));
341 1 : }
342 :
343 : // zero
344 : {
345 1 : cluck_daemon::computer c;
346 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|127.0.0.1|0|name"));
347 :
348 : // the following is a trick that currently works because the
349 : // set_id() function saves the data until the one part that
350 : // is invalid
351 : //
352 1 : CATCH_REQUIRE_THROWS_MATCHES(
353 : c.get_id()
354 : , cluck::invalid_parameter
355 : , Catch::Matchers::ExceptionMessage("cluck_exception: computer::get_id() can't be called when the pid is not defined."));
356 1 : }
357 :
358 : // negative
359 : {
360 1 : cluck_daemon::computer c;
361 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|127.0.0.1|-5501|name"));
362 1 : }
363 :
364 : // too large
365 : {
366 1 : cluck_daemon::computer c;
367 1 : std::string const count(std::to_string(cppthread::get_pid_max() + 1));
368 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|127.0.0.1|" + count + "|name"));
369 1 : }
370 : }
371 10 : CATCH_END_SECTION()
372 :
373 11 : CATCH_START_SECTION("daemon_computer_errors: invalid name")
374 : {
375 : // empty
376 : {
377 1 : cluck_daemon::computer c;
378 1 : CATCH_REQUIRE_FALSE(c.set_id("10|9001|127.0.0.1|2512|"));
379 1 : }
380 : }
381 10 : CATCH_END_SECTION()
382 9 : }
383 :
384 :
385 :
386 : // vim: ts=4 sw=4 et
|