Line data Source code
1 : // Copyright (c) 2006-2025 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snaplogger
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 2 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 along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : // self
21 : //
22 : #include "catch_main.h"
23 :
24 :
25 : // snaplogger
26 : //
27 : #include <snaplogger/buffer_appender.h>
28 : #include <snaplogger/exception.h>
29 : #include <snaplogger/format.h>
30 : #include <snaplogger/logger.h>
31 : #include <snaplogger/map_diagnostic.h>
32 : #include <snaplogger/message.h>
33 : #include <snaplogger/severity.h>
34 : #include <snaplogger/version.h>
35 :
36 :
37 : // advgetopt
38 : //
39 : #include <advgetopt/conf_file.h>
40 : #include <advgetopt/validator_integer.h>
41 :
42 :
43 : // snapdev
44 : //
45 : #include <snapdev/enum_class_math.h>
46 :
47 :
48 : // C
49 : //
50 : #include <unistd.h>
51 : #include <netdb.h>
52 : #include <sys/param.h>
53 :
54 :
55 :
56 :
57 6 : CATCH_TEST_CASE("severity", "[severity]")
58 : {
59 6 : CATCH_START_SECTION("severity: create severity")
60 : {
61 3 : CATCH_REQUIRE(snaplogger::get_severity("bad-error") == nullptr);
62 3 : CATCH_REQUIRE(snaplogger::get_severity("big-error") == nullptr);
63 :
64 1 : snaplogger::severity_t const level(static_cast<snaplogger::severity_t>(205));
65 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(level, "bad-error"));
66 :
67 1 : CATCH_REQUIRE(s->get_severity() == level);
68 1 : CATCH_REQUIRE(s->get_name() == "bad-error");
69 :
70 1 : CATCH_REQUIRE(s->get_all_names().size() == 1);
71 1 : CATCH_REQUIRE(s->get_all_names()[0] == "bad-error");
72 :
73 3 : CATCH_REQUIRE(snaplogger::get_severity("bad-error") == nullptr);
74 3 : CATCH_REQUIRE(snaplogger::get_severity("big-error") == nullptr);
75 :
76 : // duplicates (a.k.a. self) are not allowed
77 : //
78 7 : CATCH_REQUIRE_THROWS_MATCHES(
79 : s->add_alias("bad-error")
80 : , snaplogger::duplicate_error
81 : , Catch::Matchers::ExceptionMessage(
82 : "logger_error: severity \""
83 : "bad-error"
84 : "\" already has an alias \""
85 : "bad-error"
86 : "\"."));
87 :
88 1 : snaplogger::add_severity(s);
89 :
90 3 : CATCH_REQUIRE(snaplogger::get_severity("bad-error") == s);
91 3 : CATCH_REQUIRE(snaplogger::get_severity("big-error") == nullptr);
92 :
93 3 : s->add_alias("big-error");
94 :
95 : // duplicates of an alias are not allowed
96 : //
97 7 : CATCH_REQUIRE_THROWS_MATCHES(
98 : s->add_alias("big-error")
99 : , snaplogger::duplicate_error
100 : , Catch::Matchers::ExceptionMessage(
101 : "logger_error: severity \""
102 : "bad-error"
103 : "\" already has an alias \""
104 : "big-error"
105 : "\"."));
106 :
107 1 : CATCH_REQUIRE(s->get_all_names().size() == 2);
108 1 : CATCH_REQUIRE(s->get_all_names()[0] == "bad-error");
109 1 : CATCH_REQUIRE(s->get_all_names()[1] == "big-error");
110 :
111 1 : CATCH_REQUIRE(s->get_description() == "bad-error");
112 :
113 3 : s->set_description("bad error");
114 1 : CATCH_REQUIRE(s->get_description() == "bad error");
115 :
116 1 : s->set_description(std::string());
117 1 : CATCH_REQUIRE(s->get_description() == "bad-error");
118 :
119 3 : CATCH_REQUIRE(snaplogger::get_severity("bad-error") == s);
120 3 : CATCH_REQUIRE(snaplogger::get_severity("big-error") == s);
121 1 : CATCH_REQUIRE(snaplogger::get_severity(level) == s);
122 :
123 3 : s->set_styles("orange");
124 1 : CATCH_REQUIRE(s->get_styles() == "orange");
125 :
126 1 : snaplogger::severity_t const level_plus_one(static_cast<snaplogger::severity_t>(static_cast<int>(level) + 1));
127 1 : CATCH_REQUIRE(snaplogger::get_severity(level_plus_one) == nullptr);
128 :
129 1 : snaplogger::message msg(::snaplogger::severity_t::SEVERITY_ERROR);
130 3 : CATCH_REQUIRE(snaplogger::get_severity(msg, "bad-error") == s);
131 3 : CATCH_REQUIRE(snaplogger::get_severity(msg, "big-error") == s);
132 1 : }
133 6 : CATCH_END_SECTION()
134 :
135 6 : CATCH_START_SECTION("severity: test \"<name>\"_sev support")
136 : {
137 1 : snaplogger::severity_t const level(static_cast<snaplogger::severity_t>(25));
138 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(level, "remark"));
139 :
140 1 : snaplogger::add_severity(s);
141 :
142 1 : CATCH_REQUIRE(s->get_severity() == level);
143 1 : CATCH_REQUIRE(s->get_name() == "remark");
144 :
145 1 : snaplogger::severity::pointer_t r("remark"_sev);
146 1 : CATCH_REQUIRE(r == s);
147 :
148 1 : CATCH_REQUIRE(r->get_severity() == level);
149 1 : CATCH_REQUIRE(r->get_name() == "remark");
150 1 : }
151 6 : CATCH_END_SECTION()
152 :
153 6 : CATCH_START_SECTION("severity: set/get default")
154 : {
155 1 : snaplogger::logger::pointer_t l(snaplogger::logger::get_instance());
156 1 : snaplogger::severity_t const level(static_cast<snaplogger::severity_t>(57));
157 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(level, "default"));
158 :
159 1 : snaplogger::add_severity(s);
160 :
161 1 : CATCH_REQUIRE(l->set_default_severity(s->get_severity()));
162 1 : CATCH_REQUIRE(l->get_default_severity() == s->get_severity());
163 :
164 : // non-existent, the set fails
165 : //
166 1 : CATCH_REQUIRE_FALSE(l->set_default_severity(static_cast<snaplogger::severity_t>(58)));
167 :
168 : // reset to default
169 : //
170 1 : CATCH_REQUIRE(l->set_default_severity(snaplogger::severity_t::SEVERITY_ALL));
171 1 : CATCH_REQUIRE(l->get_default_severity() == snaplogger::severity_t::SEVERITY_DEFAULT);
172 1 : }
173 6 : CATCH_END_SECTION()
174 :
175 6 : CATCH_START_SECTION("severity: print severity")
176 : {
177 : struct level_and_name_t
178 : {
179 : ::snaplogger::severity_t f_level = ::snaplogger::severity_t::SEVERITY_ERROR;
180 : std::string f_name = std::string();
181 : };
182 :
183 1 : std::vector<level_and_name_t> level_and_name =
184 : {
185 : { ::snaplogger::severity_t::SEVERITY_ALL, "all" },
186 : { ::snaplogger::severity_t::SEVERITY_TRACE, "trace" },
187 : { ::snaplogger::severity_t::SEVERITY_NOISY, "noisy" },
188 : { ::snaplogger::severity_t::SEVERITY_DEBUG, "debug" },
189 : { ::snaplogger::severity_t::SEVERITY_NOTICE, "notice" },
190 : { ::snaplogger::severity_t::SEVERITY_UNIMPORTANT, "unimportant" },
191 : { ::snaplogger::severity_t::SEVERITY_VERBOSE, "verbose" },
192 : { ::snaplogger::severity_t::SEVERITY_CONFIGURATION, "configuration" },
193 : { ::snaplogger::severity_t::SEVERITY_CONFIGURATION_WARNING, "configuration-warning" },
194 : { ::snaplogger::severity_t::SEVERITY_INFORMATION, "information" },
195 : { ::snaplogger::severity_t::SEVERITY_IMPORTANT, "important" },
196 : { ::snaplogger::severity_t::SEVERITY_MINOR, "minor" },
197 : { ::snaplogger::severity_t::SEVERITY_DEPRECATED, "deprecated" },
198 : { ::snaplogger::severity_t::SEVERITY_WARNING, "warning" },
199 : { ::snaplogger::severity_t::SEVERITY_MAJOR, "major" },
200 : { ::snaplogger::severity_t::SEVERITY_RECOVERABLE_ERROR, "recoverable-error" },
201 : { ::snaplogger::severity_t::SEVERITY_ERROR, "error" },
202 : { ::snaplogger::severity_t::SEVERITY_NOISY_ERROR, "noisy-error" },
203 : { ::snaplogger::severity_t::SEVERITY_SEVERE, "severe" },
204 : { ::snaplogger::severity_t::SEVERITY_EXCEPTION, "exception" },
205 : { ::snaplogger::severity_t::SEVERITY_CRITICAL, "critical" },
206 : { ::snaplogger::severity_t::SEVERITY_ALERT, "alert" },
207 : { ::snaplogger::severity_t::SEVERITY_EMERGENCY, "emergency" },
208 : { ::snaplogger::severity_t::SEVERITY_FATAL, "fatal" },
209 : { ::snaplogger::severity_t::SEVERITY_OFF, "off" },
210 29 : };
211 :
212 26 : for(auto const & ln : level_and_name)
213 : {
214 25 : std::stringstream buffer;
215 25 : buffer << ln.f_level;
216 25 : CATCH_REQUIRE(buffer.str() == ln.f_name);
217 25 : }
218 :
219 : {
220 1 : std::stringstream buffer;
221 1 : buffer << static_cast<::snaplogger::severity_t>(254);
222 1 : CATCH_REQUIRE(buffer.str() == "(unknown severity: 254)");
223 1 : }
224 1 : }
225 6 : CATCH_END_SECTION()
226 :
227 6 : CATCH_START_SECTION("severity: severity by level or name")
228 : {
229 1 : snaplogger::severity_by_severity_t severities(snaplogger::get_severities_by_severity());
230 1 : snaplogger::severity_by_name_t names(snaplogger::get_severities_by_name());
231 :
232 : // count names that are not aliases
233 : //
234 1 : std::size_t count(0);
235 48 : for(auto const & s : names)
236 : {
237 47 : if(s.second->get_name() == s.first)
238 : {
239 29 : ++count;
240 : }
241 : }
242 1 : CATCH_REQUIRE(count == severities.size());
243 :
244 : // this does not hold true, that is, the map is properly sorted, but for
245 : // entries with an alias, the s->get_name() returns the base name, not the
246 : // alias, and thus, the order may be skewed for all aliases
247 : //
248 : // std::string previous_name;
249 : // for(auto const & s : names)
250 : // {
251 : // CATCH_REQUIRE(s.second->get_name() > previous_name);
252 : // previous_name = s.second->get_name();
253 : // }
254 :
255 1 : snaplogger::severity_t previous_severity(snaplogger::severity_t::SEVERITY_ALL - 1);
256 30 : for(auto const & s : severities)
257 : {
258 29 : CATCH_REQUIRE(s.second->get_severity() > previous_severity);
259 29 : previous_severity = s.second->get_severity();
260 : }
261 1 : }
262 6 : CATCH_END_SECTION()
263 :
264 6 : CATCH_START_SECTION("severity: severity.ini file matches")
265 : {
266 : // whenever I make an edit to the list of severity levels, I have to
267 : // keep the severity.ini up to date or the system won't be able to
268 : // load the file properly; this test verifies the one found in the
269 : // source tree against the severity levels defined in the header
270 : //
271 1 : std::string severity_ini_filename(SNAP_CATCH2_NAMESPACE::g_source_dir());
272 1 : severity_ini_filename += "/conf/severity.ini";
273 1 : advgetopt::conf_file_setup setup(severity_ini_filename);
274 1 : CATCH_REQUIRE(setup.is_valid());
275 :
276 1 : advgetopt::conf_file::pointer_t severity_ini(advgetopt::conf_file::get_conf_file(setup));
277 1 : CATCH_REQUIRE(severity_ini != nullptr);
278 :
279 1 : std::set<std::string> found;
280 :
281 1 : advgetopt::conf_file::sections_t sections(severity_ini->get_sections());
282 27 : for(auto const & s : sections)
283 : {
284 26 : snaplogger::severity::pointer_t severity(snaplogger::get_severity(s));
285 :
286 : // the default severity.ini file only defines system severities
287 : //
288 26 : CATCH_REQUIRE(severity->is_system());
289 :
290 : // make sure the entry includes a severity=... value
291 : //
292 26 : std::string const level_name(s + "::severity");
293 26 : CATCH_REQUIRE(severity_ini->has_parameter(level_name));
294 :
295 26 : std::string const level(severity_ini->get_parameter(level_name));
296 :
297 : // the level must be a valid integer
298 : //
299 26 : std::int64_t value(0);
300 26 : CATCH_REQUIRE(advgetopt::validator_integer::convert_string(level, value));
301 :
302 : // the .ini level must match the internal (library) level
303 : //
304 26 : CATCH_REQUIRE(severity->get_severity() == static_cast<snaplogger::severity_t>(value));
305 :
306 26 : found.insert(s);
307 :
308 : // severities may have aliases which needs to be added to `found`
309 : //
310 26 : std::string const aliases_name(s + "::aliases");
311 26 : if(severity_ini->has_parameter(aliases_name))
312 : {
313 17 : std::string const aliases(severity_ini->get_parameter(aliases_name));
314 17 : advgetopt::string_list_t names;
315 51 : advgetopt::split_string(aliases, names, {","});
316 35 : for(auto const & n : names)
317 : {
318 18 : found.insert(n);
319 : }
320 17 : }
321 :
322 : // verify mismatches in other parameters if defined
323 : //
324 26 : std::string const description_name(s + "::description");
325 26 : if(severity_ini->has_parameter(description_name))
326 : {
327 25 : std::string const description(severity_ini->get_parameter(description_name));
328 25 : CATCH_REQUIRE(severity->get_description() == description);
329 25 : }
330 :
331 26 : std::string const styles_name(s + "::styles");
332 26 : if(severity_ini->has_parameter(styles_name))
333 : {
334 17 : std::string const styles(severity_ini->get_parameter(styles_name));
335 17 : CATCH_REQUIRE(severity->get_styles() == styles);
336 17 : }
337 :
338 26 : std::string const default_name(s + "::default");
339 26 : if(severity_ini->has_parameter(default_name))
340 : {
341 1 : std::string const default_value(severity_ini->get_parameter(default_name));
342 1 : bool const valid_default(advgetopt::is_true(default_value) || advgetopt::is_false(default_value));
343 1 : CATCH_REQUIRE(valid_default);
344 1 : }
345 26 : }
346 :
347 : // make sure all the parameters are known
348 : // (for our file, that's best in case we misspell something)
349 : //
350 1 : std::string default_severity;
351 87 : for(auto const & p : severity_ini->get_parameters())
352 : {
353 86 : advgetopt::string_list_t names;
354 258 : advgetopt::split_string(p.first, names, {"::"});
355 :
356 : // first of all, we do not support global definition in the
357 : // severity.ini file so we should only have names within a
358 : // section and no sub-section thus two names exactly
359 : //
360 86 : CATCH_REQUIRE(names.size() == 2);
361 :
362 86 : if(names[1] == "default")
363 : {
364 1 : if(advgetopt::is_true(p.second))
365 : {
366 : // there can be only one default
367 : //
368 1 : if(!default_severity.empty())
369 : {
370 : std::cerr
371 : << "--- found more than one default: \""
372 : << default_severity
373 : << "\" and \""
374 0 : << names[0]
375 0 : << "\"\n";
376 : }
377 1 : CATCH_REQUIRE(default_severity.empty());
378 :
379 1 : default_severity = names[0];
380 : }
381 : }
382 85 : else if(names[1] != "aliases"
383 68 : && names[1] != "description"
384 43 : && names[1] != "severity"
385 153 : && names[1] != "styles")
386 : {
387 0 : std::cerr << "--- found unknown parameter \"" << names[1] << "\"\n";
388 0 : CATCH_REQUIRE(!"parameter not known");
389 : }
390 87 : }
391 :
392 : // further, make sure that all system severities defined in the
393 : // library are found in the .ini file
394 : //
395 : // with aliases, this is not 100% true, maybe we should check
396 : // with the by_severity() list instead...
397 : //
398 48 : for(auto const & s : snaplogger::get_severities_by_name())
399 : {
400 47 : if(s.second->is_system())
401 : {
402 43 : bool const contained(found.contains(s.first));
403 43 : if(!contained)
404 : {
405 : std::cout
406 : << "--- system severity \""
407 0 : << s.first
408 : << "\" is not defined in \""
409 : << severity_ini_filename
410 0 : << "\" file.\n";
411 : }
412 43 : CATCH_REQUIRE(contained);
413 : }
414 1 : }
415 1 : }
416 6 : CATCH_END_SECTION()
417 9 : }
418 :
419 :
420 5 : CATCH_TEST_CASE("severity_error", "[severity][error]")
421 : {
422 5 : CATCH_START_SECTION("severity: severity by name using an existing system severity name (\"error\")")
423 : {
424 1 : snaplogger::severity_t const err_plus_one(static_cast<snaplogger::severity_t>(static_cast<int>(snaplogger::severity_t::SEVERITY_ERROR) + 1));
425 :
426 : // user severity by name
427 : {
428 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(err_plus_one, "error"));
429 :
430 5 : CATCH_REQUIRE_THROWS_MATCHES(
431 : snaplogger::add_severity(s)
432 : , snaplogger::duplicate_error
433 : , Catch::Matchers::ExceptionMessage(
434 : "logger_error: a system severity (error) cannot be replaced (same name)."));
435 1 : }
436 :
437 : // system severity by name
438 : {
439 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(err_plus_one, "error", true));
440 :
441 5 : CATCH_REQUIRE_THROWS_MATCHES(
442 : snaplogger::add_severity(s)
443 : , snaplogger::duplicate_error
444 : , Catch::Matchers::ExceptionMessage(
445 : "logger_error: a system severity (error) cannot be replaced (same name)."));
446 1 : }
447 : }
448 5 : CATCH_END_SECTION()
449 :
450 5 : CATCH_START_SECTION("severity: severity by severity using an existing system severity")
451 : {
452 : // user severity by severity
453 : {
454 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(snaplogger::severity_t::SEVERITY_ERROR, "duplicate-error"));
455 :
456 3 : CATCH_REQUIRE_THROWS_MATCHES(
457 : snaplogger::add_severity(s)
458 : , snaplogger::duplicate_error
459 : , Catch::Matchers::ExceptionMessage(
460 : "logger_error: a system severity ("
461 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_ERROR))
462 : + ") cannot be replaced (same severity level: "
463 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_ERROR))
464 : + ")."));
465 1 : }
466 :
467 : // system severity by severity
468 : {
469 1 : snaplogger::severity::pointer_t s(std::make_shared<snaplogger::severity>(snaplogger::severity_t::SEVERITY_ERROR, "duplicate-error", true));
470 :
471 3 : CATCH_REQUIRE_THROWS_MATCHES(
472 : snaplogger::add_severity(s)
473 : , snaplogger::duplicate_error
474 : , Catch::Matchers::ExceptionMessage(
475 : "logger_error: a system severity ("
476 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_ERROR))
477 : + ") cannot be replaced (same severity level: "
478 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_ERROR))
479 : + ")."));
480 1 : }
481 : }
482 5 : CATCH_END_SECTION()
483 :
484 5 : CATCH_START_SECTION("severity: severity alias cannot be an existing system severity")
485 : {
486 1 : snaplogger::severity::pointer_t fatal("fatal"_sev);
487 :
488 7 : CATCH_REQUIRE_THROWS_MATCHES(
489 : fatal->add_alias("emerg")
490 : , snaplogger::duplicate_error
491 : , Catch::Matchers::ExceptionMessage(
492 : "logger_error: a system severity (emerg) cannot be replaced (same name)."));
493 1 : }
494 5 : CATCH_END_SECTION()
495 :
496 5 : CATCH_START_SECTION("severity: too small")
497 : {
498 6 : CATCH_REQUIRE_THROWS_MATCHES(
499 : snaplogger::severity(snaplogger::severity_t::SEVERITY_MIN - 1, "TOO_SMALL")
500 : , snaplogger::invalid_severity
501 : , Catch::Matchers::ExceptionMessage(
502 : "logger_error: the severity level cannot be "
503 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MIN - 1))
504 : + ". The possible range is ["
505 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MIN))
506 : + ".."
507 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MAX))
508 : + "]."));
509 : }
510 5 : CATCH_END_SECTION()
511 :
512 5 : CATCH_START_SECTION("severity: too large")
513 : {
514 6 : CATCH_REQUIRE_THROWS_MATCHES(
515 : snaplogger::severity(snaplogger::severity_t::SEVERITY_MAX + 1, "TOO_LARGE")
516 : , snaplogger::invalid_severity
517 : , Catch::Matchers::ExceptionMessage(
518 : "logger_error: the severity level cannot be "
519 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MAX + 1))
520 : + ". The possible range is ["
521 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MIN))
522 : + ".."
523 : + std::to_string(static_cast<int>(snaplogger::severity_t::SEVERITY_MAX))
524 : + "]."));
525 : }
526 5 : CATCH_END_SECTION()
527 5 : }
528 :
529 :
530 :
531 : // vim: ts=4 sw=4 et
|