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