Branch data Line data Source code
1 : : #include "test_librational_all.h"
2 : : #include <errno.h>
3 : :
4 : : static const char expected_report_stderr_pattern[] =
5 : : "\\A"
6 : : "ERROR: [^\\n]*src/test_librational_0004\\.c:report_test:\\d+ Memory reallocation failed with bytes 10 Errno: [^\\n]+ \\(errno: [0-9]+\\)\n"
7 : : "ERROR: [^\\n]*src/test_librational_0004\\.c:report_test:\\d+ Buffer overflow at position 42 with value overflow Errno: [^\\n]+ \\(errno: [0-9]+\\)\\Z";
8 : :
9 : : /* Regex fragments for slog() output fields that change between source layouts and test runs */
10 : : #define SLOG_TIME_PATTERN "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{3} "
11 : : #define SLOG_SOURCE_PATTERN "[^\\n]*src/test_librational_0004\\.c:[0-9]+:slog_test:"
12 : : #define SLOG_DECORATED_PATTERN SLOG_TIME_PATTERN SLOG_SOURCE_PATTERN
13 : :
14 : : static const char expected_slog_stdout_pattern[] =
15 : : "\\A"
16 : : "All available combinations:\n"
17 : : "REGULAR\n"
18 : : "VERBOSE\n"
19 : : "TESTING\n"
20 : : "SILENT\n"
21 : : "REGULAR\\|VERBOSE\n"
22 : : "REGULAR\\|TESTING\n"
23 : : "VERBOSE\\|TESTING\n"
24 : : "REGULAR\\|VERBOSE\\|TESTING\n"
25 : : "ERROR\n"
26 : : "UNDECOR\n"
27 : : "EVERY\\|UNDECOR\n"
28 : : "ERROR\\|UNDECOR\n"
29 : : "VISIBLE_IN_SILENT\n"
30 : : "Mode: REGULAR\n"
31 : : "1\\. Must print:true\n"
32 : : "2\\. Won't print:\n"
33 : : "3\\. Won't print:\n"
34 : : "4\\. Must print:ERROR: true\n"
35 : : "Mode: VERBOSE\n"
36 : : "5\\. Won't print:\n"
37 : : "6\\. Must print:" SLOG_DECORATED_PATTERN "true\n"
38 : : "7\\. Won't print:\n"
39 : : "8\\. Must print:" SLOG_DECORATED_PATTERN "ERROR:true\n"
40 : : "Mode: TESTING\n"
41 : : "9\\. Won't print:\n"
42 : : "10\\. Won't print:\n"
43 : : "11\\. Must print:TESTING:true\n"
44 : : "12\\. Must print:ERROR:true\n"
45 : : "Mode: SILENT\n"
46 : : "13\\. Won't print:\n"
47 : : "14\\. Won't print:\n"
48 : : "15\\. Won't print:\n"
49 : : "16\\. Won't print:\n"
50 : : "Mode: REGULAR \\| VERBOSE\n"
51 : : "17\\. Must print:true\n"
52 : : "18\\. Must print:" SLOG_DECORATED_PATTERN "true\n"
53 : : "19\\. Won't print:\n"
54 : : "20\\. Must print:" SLOG_DECORATED_PATTERN "ERROR: true\n"
55 : : "Mode: REGULAR \\| TESTING\n"
56 : : "21\\. Must print:true\n"
57 : : "22\\. Won't print:\n"
58 : : "23\\. Must print:TESTING:true\n"
59 : : "24\\. Must print:ERROR: true\n"
60 : : "Mode: VERBOSE \\| TESTING\n"
61 : : "25\\. Won't print:\n"
62 : : "26\\. Must print:" SLOG_DECORATED_PATTERN "true\n"
63 : : "27\\. Must print:TESTING:true\n"
64 : : "28\\. Must print:" SLOG_DECORATED_PATTERN "ERROR:true\n"
65 : : "Mode: REGULAR \\| VERBOSE \\| TESTING\n"
66 : : "29\\. Must print:true\n"
67 : : "30\\. Must print:" SLOG_DECORATED_PATTERN "true\n"
68 : : "31\\. Must print:TESTING:true\n"
69 : : "32\\. Must print:" SLOG_DECORATED_PATTERN "ERROR: true\n"
70 : : "Mode: ERROR\n"
71 : : "33\\. Won't print:\n"
72 : : "34\\. Won't print:\n"
73 : : "35\\. Won't print:\n"
74 : : "36\\. Must print:ERROR: true\n"
75 : : "Mode: REGULAR \\| VERBOSE \\| TESTING \\| ERROR\n"
76 : : "37\\. Must print no prefixes:\\|true\\|\n"
77 : : "38\\. Must print no ERROR prefix:\\|true\\|\n"
78 : : "Mode: VERBOSE\n"
79 : : "39\\. Must print no time/file/line/func:\\|true\\|\n"
80 : : "Mode: TESTING\n"
81 : : "40\\. Must print no TESTING prefix:\\|true\\|\n"
82 : : "Mode: REGULAR\n"
83 : : "41\\. Must not print \\(VERBOSE not enabled\\):\\|\\|\n"
84 : : "Mode: SILENT\n"
85 : : "42\\. Must print in SILENT without prefixes:\\|true\\|\n"
86 : : "43\\. Must print no ERROR prefix in SILENT:\\|true\\|\\Z";
87 : :
88 : : static const char expected_serp_stderr_pattern[] =
89 : : "\\A"
90 : : "ERROR: Failed to open file \\[File: [^\\n]*src/test_librational_0004\\.c, Function: serp_case\\] Errno: \\(2\\) No such file or directory\\Z";
91 : :
92 : : #ifndef EVIL_EMPIRE_OS
93 : : static const char expected_write_fallback_stderr_pattern[] =
94 : : "\\AERROR: Failed to write error message\n\\Z";
95 : :
96 : : static const char expected_logger_time_failure_stdout_pattern[] =
97 : : "\\A"
98 : : " [^\\n]*src/test_librational_0004\\.c:[0-9]+:logger_time_failure_cases:gettimeofday failure\n"
99 : : " [^\\n]*src/test_librational_0004\\.c:[0-9]+:logger_time_failure_cases:localtime_r failure\n"
100 : : " [^\\n]*src/test_librational_0004\\.c:[0-9]+:logger_time_failure_cases:snprintf failure\\Z";
101 : : #endif
102 : :
103 : : /**
104 : : * @brief Emit two report() messages with controlled errno values
105 : : */
106 : 1 : static void report_test(void)
107 : : {
108 : 1 : errno = ENOMEM;
109 : :
110 : : /* report() must include the formatted message and decoded errno */
111 : 1 : report("Memory reallocation failed with bytes %d",10);
112 : :
113 : 1 : errno = EOVERFLOW;
114 : :
115 : : /* The second call verifies that several printf arguments are preserved */
116 : 1 : report("Buffer overflow at position %d with value %s",42,"overflow");
117 : 1 : }
118 : :
119 : : /**
120 : : * @brief Emit the complete slog() mode matrix used by the strict stdout pattern
121 : : */
122 : 1 : static void slog_test(void)
123 : : {
124 : 1 : printf("All available combinations:\n");
125 : 1 : printf("%s\n",rational_convert(REGULAR));
126 : 1 : printf("%s\n",rational_convert(VERBOSE));
127 : 1 : printf("%s\n",rational_convert(TESTING));
128 : 1 : printf("%s\n",rational_convert(SILENT));
129 : 1 : printf("%s\n",rational_convert(REGULAR|VERBOSE));
130 : 1 : printf("%s\n",rational_convert(REGULAR|TESTING));
131 : 1 : printf("%s\n",rational_convert(VERBOSE|TESTING));
132 : 1 : printf("%s\n",rational_convert(REGULAR|VERBOSE|TESTING));
133 : 1 : printf("%s\n",rational_convert(ERROR));
134 : 1 : printf("%s\n",rational_convert(UNDECOR));
135 : 1 : printf("%s\n",rational_convert(EVERY|UNDECOR));
136 : 1 : printf("%s\n",rational_convert(ERROR|UNDECOR));
137 : 1 : printf("%s\n",rational_convert(VISIBLE_IN_SILENT));
138 : :
139 : : /* Test REGULAR mode combinations */
140 : 1 : rational_logger_mode = REGULAR;
141 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
142 : 1 : printf("1. Must print:"); slog(REGULAR,"true"); printf("\n");
143 : 1 : printf("2. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
144 : 1 : printf("3. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
145 : 1 : printf("4. Must print:"); slog(ERROR,"true"); printf("\n");
146 : :
147 : : /* Test VERBOSE mode combinations */
148 : 1 : rational_logger_mode = VERBOSE;
149 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
150 : 1 : printf("5. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
151 : 1 : printf("6. Must print:"); slog(VERBOSE,"true"); printf("\n");
152 : 1 : printf("7. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
153 : 1 : printf("8. Must print:"); slog(ERROR,"true"); printf("\n");
154 : :
155 : : /* Test TESTING mode combinations */
156 : 1 : rational_logger_mode = TESTING;
157 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
158 : 1 : printf("9. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
159 : 1 : printf("10. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
160 : 1 : printf("11. Must print:"); slog(TESTING,"true"); printf("\n");
161 : 1 : printf("12. Must print:"); slog(ERROR,"true"); printf("\n");
162 : :
163 : : /* Test SILENT mode combinations */
164 : 1 : rational_logger_mode = SILENT;
165 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
166 : 1 : printf("13. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
167 : 1 : printf("14. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
168 : 1 : printf("15. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
169 : 1 : printf("16. Won't print:"); slog(ERROR,"but printed!"); printf("\n");
170 : :
171 : : /* Test REGULAR|VERBOSE combinations */
172 : 1 : rational_logger_mode = REGULAR|VERBOSE;
173 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
174 : 1 : printf("17. Must print:"); slog(REGULAR,"true"); printf("\n");
175 : 1 : printf("18. Must print:"); slog(VERBOSE,"true"); printf("\n");
176 : 1 : printf("19. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
177 : 1 : printf("20. Must print:"); slog(ERROR,"true"); printf("\n");
178 : :
179 : : /* Test REGULAR|TESTING combinations */
180 : 1 : rational_logger_mode = REGULAR|TESTING;
181 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
182 : 1 : printf("21. Must print:"); slog(REGULAR,"true"); printf("\n");
183 : 1 : printf("22. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
184 : 1 : printf("23. Must print:"); slog(TESTING,"true"); printf("\n");
185 : 1 : printf("24. Must print:"); slog(ERROR,"true"); printf("\n");
186 : :
187 : : /* Test VERBOSE|TESTING combinations */
188 : 1 : rational_logger_mode = VERBOSE|TESTING;
189 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
190 : 1 : printf("25. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
191 : 1 : printf("26. Must print:"); slog(VERBOSE,"true"); printf("\n");
192 : 1 : printf("27. Must print:"); slog(TESTING,"true"); printf("\n");
193 : 1 : printf("28. Must print:"); slog(ERROR,"true"); printf("\n");
194 : :
195 : : /* Test REGULAR|VERBOSE|TESTING combinations */
196 : 1 : rational_logger_mode = REGULAR|VERBOSE|TESTING;
197 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
198 : 1 : printf("29. Must print:"); slog(REGULAR,"true"); printf("\n");
199 : 1 : printf("30. Must print:"); slog(VERBOSE,"true"); printf("\n");
200 : 1 : printf("31. Must print:"); slog(TESTING,"true"); printf("\n");
201 : 1 : printf("32. Must print:"); slog(ERROR,"true"); printf("\n");
202 : :
203 : : /* Test ERROR mode combinations */
204 : 1 : rational_logger_mode = ERROR;
205 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
206 : 1 : printf("33. Won't print:"); slog(REGULAR,"but printed!"); printf("\n");
207 : 1 : printf("34. Won't print:"); slog(VERBOSE,"but printed!"); printf("\n");
208 : 1 : printf("35. Won't print:"); slog(TESTING,"but printed!"); printf("\n");
209 : 1 : printf("36. Must print:"); slog(ERROR,"true"); printf("\n");
210 : :
211 : : /*
212 : : * Test UNDECOR flag: suppress logger prefixes (TESTING:, time/file/line/func, ERROR:)
213 : : * The output between the '|' markers should contain only the message payload
214 : : */
215 : 1 : rational_logger_mode = EVERY|ERROR;
216 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
217 : 1 : printf("37. Must print no prefixes:|"); slog(EVERY|UNDECOR,"true"); printf("|\n");
218 : 1 : printf("38. Must print no ERROR prefix:|"); slog(ERROR|UNDECOR,"true"); printf("|\n");
219 : :
220 : 1 : rational_logger_mode = VERBOSE;
221 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
222 : 1 : printf("39. Must print no time/file/line/func:|"); slog(VERBOSE|UNDECOR,"true"); printf("|\n");
223 : :
224 : 1 : rational_logger_mode = TESTING;
225 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
226 : 1 : printf("40. Must print no TESTING prefix:|"); slog(TESTING|UNDECOR,"true"); printf("|\n");
227 : :
228 : 1 : rational_logger_mode = REGULAR;
229 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
230 : 1 : printf("41. Must not print (VERBOSE not enabled):|"); slog(VERBOSE|UNDECOR,"but printed!"); printf("|\n");
231 : :
232 : 1 : rational_logger_mode = SILENT;
233 : 1 : printf("Mode: %s\n",rational_reconvert(rational_logger_mode));
234 : 1 : printf("42. Must print in SILENT without prefixes:|"); slog(EVERY|VISIBLE_IN_SILENT,"true"); printf("|\n");
235 : 1 : printf("43. Must print no ERROR prefix in SILENT:|"); slog(ERROR|VISIBLE_IN_SILENT,"true"); printf("|\n");
236 : 1 : }
237 : :
238 : : /**
239 : : * @brief Emit one serp() message with a controlled missing-file errno
240 : : */
241 : 1 : static void serp_case(void)
242 : : {
243 : 1 : errno = ENOENT;
244 : 1 : serp("Failed to open file");
245 : 1 : }
246 : :
247 : : /**
248 : : * @brief Do nothing while function_capture() checks pending stdout behavior
249 : : */
250 : 1 : static void no_output_test(void)
251 : : {
252 : 1 : }
253 : :
254 : : #ifndef EVIL_EMPIRE_OS
255 : : /**
256 : : * @brief Exercise slog() when timestamp construction helpers fail
257 : : */
258 : 1 : static void logger_time_failure_cases(void)
259 : : {
260 : 1 : rational_logger_mode = VERBOSE;
261 : :
262 : : /* Missing timestamp data should leave an empty prefix slot but keep the message */
263 : 1 : testmocking_gettimeofday_fail_next(1);
264 : 1 : slog(VERBOSE,"gettimeofday failure");
265 : 1 : printf("\n");
266 : 1 : testmocking_gettimeofday_disable();
267 : :
268 : : /* localtime_r() failure follows the same fallback path */
269 : 1 : testmocking_localtime_r_fail_next(1);
270 : 1 : slog(VERBOSE,"localtime_r failure");
271 : 1 : printf("\n");
272 : 1 : testmocking_localtime_r_disable();
273 : :
274 : : /* snprintf() failure while formatting the timestamp must not hide the payload */
275 : 1 : testmocking_snprintf_fail_next(1);
276 : 1 : slog(VERBOSE,"snprintf failure");
277 : 1 : testmocking_snprintf_disable();
278 : :
279 : 1 : rational_logger_mode = REGULAR;
280 : 1 : }
281 : :
282 : : /**
283 : : * @brief Emit one report() message with a controlled I/O errno
284 : : */
285 : 4 : static void report_single_case(void)
286 : : {
287 : 4 : errno = EIO;
288 : 4 : report("Mocked report output");
289 : 4 : }
290 : :
291 : : /**
292 : : * @brief Emit one report() message while snprintf is forced to truncate
293 : : */
294 : 1 : static void report_truncated_case(void)
295 : : {
296 : 1 : testmocking_snprintf_truncate_next(1);
297 : 1 : report_single_case();
298 : 1 : testmocking_snprintf_disable();
299 : 1 : }
300 : :
301 : : /**
302 : : * @brief Capture slog() output when line allocation or sanitization fails
303 : : *
304 : : * @return Return describing success or failure
305 : : */
306 : 1 : static Return capture_librational_logger_allocation_failures(void)
307 : : {
308 : 1 : INITTEST;
309 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
310 : 1 : const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
311 : :
312 : : /* The logger should stay silent when it cannot grow the output line */
313 : 1 : testmocking_realloc_fail_next(1);
314 : 1 : rational_logger_mode = REGULAR;
315 : 1 : slog(REGULAR|UNDECOR,"hidden");
316 : 1 : testmocking_realloc_disable();
317 : :
318 : : /* Failed sanitization must not expose raw terminal controls */
319 : 1 : testmocking_malloc_fail_next(1);
320 : 1 : rational_color_mode = COLOR_MODE_NEVER;
321 : 1 : slog(REGULAR|UNDECOR,"%s","\033[31mhidden");
322 : 1 : testmocking_malloc_disable();
323 : :
324 : 1 : rational_color_mode = initial_color_mode;
325 : 1 : rational_logger_mode = initial_logger_mode;
326 : :
327 : 1 : deliver(status);
328 : : }
329 : :
330 : : /**
331 : : * @brief Require prefix failures to stop further formatting and allocation
332 : : *
333 : : * @return Return describing success or failure
334 : : */
335 : 1 : static Return capture_librational_logger_prefix_failures(void)
336 : : {
337 : 1 : INITTEST;
338 : 1 : const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
339 : 1 : const int initial_errno = errno;
340 : : static const struct {
341 : : LOGMODES mode;
342 : : LOGMODES level;
343 : : } prefix_cases[] = {
344 : : {TESTING,TESTING},
345 : : {VERBOSE,VERBOSE},
346 : : {REGULAR,ERROR},
347 : : {TESTING,ERROR},
348 : : {TESTING|VERBOSE,TESTING|VERBOSE}
349 : : };
350 : :
351 [ + + ]: 6 : for(size_t i = 0U; i < sizeof(prefix_cases) / sizeof(prefix_cases[0]); i++)
352 : : {
353 : : /* A prefix size failure must consume only one mocked call. The next
354 : : undecorated message consumes the second failure and also stays silent.
355 : : Fail timestamp formatting separately so its snprintf wrapper does not
356 : : consume either vsnprintf failure */
357 : 5 : rational_logger_mode = prefix_cases[i].mode;
358 : 5 : testmocking_snprintf_fail_next(1);
359 : 5 : testmocking_vsnprintf_fail_next(2);
360 : 5 : errno = EDOM;
361 : 5 : slog(prefix_cases[i].level,"hidden payload");
362 : 5 : const int formatting_errno = errno;
363 : 5 : testmocking_snprintf_disable();
364 : 5 : rational_logger_mode = REGULAR;
365 : 5 : slog(REGULAR|UNDECOR,"unexpected formatting after prefix failure");
366 : 5 : testmocking_vsnprintf_disable();
367 : 5 : ASSERT(formatting_errno == EDOM);
368 : :
369 : : /* The same short-circuit contract applies when prefix allocation fails */
370 : 5 : rational_logger_mode = prefix_cases[i].mode;
371 : 5 : testmocking_realloc_fail_next(2);
372 : 5 : errno = EDOM;
373 : 5 : slog(prefix_cases[i].level,"hidden payload");
374 : 5 : const int allocation_errno = errno;
375 : 5 : rational_logger_mode = REGULAR;
376 : 5 : slog(REGULAR|UNDECOR,"unexpected allocation after prefix failure");
377 : 5 : testmocking_realloc_disable();
378 : 5 : ASSERT(allocation_errno == EDOM);
379 : : }
380 : :
381 : 1 : rational_logger_mode = initial_logger_mode;
382 : 1 : errno = initial_errno;
383 : :
384 : 1 : deliver(status);
385 : : }
386 : :
387 : : /**
388 : : * @brief Capture slog() output from timestamp failure scenarios
389 : : *
390 : : * @return Return describing success or failure
391 : : */
392 : 1 : static Return capture_librational_logger_time_failures(void)
393 : : {
394 : 1 : INITTEST;
395 : :
396 : : /* Cleanup disables every mock in case the captured scenario exits early */
397 : 1 : logger_time_failure_cases();
398 : 1 : rational_logger_mode = REGULAR;
399 : 1 : testmocking_gettimeofday_disable();
400 : 1 : testmocking_localtime_r_disable();
401 : 1 : testmocking_snprintf_disable();
402 : 1 : testmocking_write_disable();
403 : :
404 : 1 : deliver(status);
405 : : }
406 : :
407 : : /**
408 : : * @brief Capture slog() output when vsnprintf fails before line allocation
409 : : *
410 : : * @return Return describing success or failure
411 : : */
412 : 1 : static Return capture_librational_logger_vsnprintf_failure(void)
413 : : {
414 : 1 : INITTEST;
415 : :
416 : : /* A failed payload size calculation should suppress the incomplete line */
417 : 1 : testmocking_vsnprintf_fail_next(1);
418 : 1 : rational_logger_mode = REGULAR;
419 : 1 : slog(REGULAR|UNDECOR,"hidden");
420 : 1 : rational_logger_mode = REGULAR;
421 : 1 : testmocking_vsnprintf_disable();
422 : :
423 : 1 : deliver(status);
424 : : }
425 : : #endif
426 : :
427 : : /**
428 : : * @brief Capture an ERROR log while no logger mode accepts it
429 : : *
430 : : * @return Return describing success or failure
431 : : */
432 : 1 : static Return capture_librational_logger_disabled_error_mode(void)
433 : : {
434 : 1 : INITTEST;
435 : :
436 : : /* With no active mode bits, even ERROR must stay silent */
437 : 1 : rational_logger_mode = (LOGMODES)0;
438 : 1 : slog(ERROR,"hidden");
439 : 1 : rational_logger_mode = REGULAR;
440 : :
441 : 1 : deliver(status);
442 : : }
443 : :
444 : : #ifndef EVIL_EMPIRE_OS
445 : : /**
446 : : * @brief Capture REMEMBER calls that have no usable payload
447 : : *
448 : : * @return Return describing success or failure
449 : : */
450 : 1 : static Return capture_librational_logger_remember_without_payload(void)
451 : : {
452 : 1 : INITTEST;
453 : :
454 : : /* Empty payload produces no remembered or printed line */
455 : 1 : rational_logger_mode = REGULAR;
456 : 1 : slog(REGULAR|UNDECOR|REMEMBER,"");
457 : :
458 : : /* Failed formatting also leaves REMEMBER without a payload to deliver */
459 : 1 : testmocking_vsnprintf_fail_next(1);
460 : 1 : slog(REGULAR|UNDECOR|REMEMBER,"hidden");
461 : 1 : testmocking_vsnprintf_disable();
462 : :
463 : 1 : rational_logger_mode = REGULAR;
464 : :
465 : 1 : deliver(status);
466 : : }
467 : : #endif
468 : :
469 : : /**
470 : : * @brief Capture the basic report() stderr contract
471 : : *
472 : : * @return Return describing success or failure
473 : : */
474 : 1 : static Return capture_librational_report_test(void)
475 : : {
476 : 1 : INITTEST;
477 : :
478 : 1 : report_test();
479 : :
480 : 1 : deliver(status);
481 : : }
482 : :
483 : : /**
484 : : * @brief Capture the full slog() mode matrix
485 : : *
486 : : * @return Return describing success or failure
487 : : */
488 : 1 : static Return capture_librational_slog_test(void)
489 : : {
490 : 1 : INITTEST;
491 : :
492 : : /* Reset the global logger mode after the matrix leaves it in SILENT */
493 : 1 : slog_test();
494 : 1 : rational_logger_mode = REGULAR;
495 : :
496 : 1 : deliver(status);
497 : : }
498 : :
499 : : /**
500 : : * @brief Capture a log line that contains unsafe terminal bytes
501 : : */
502 : 1 : static void librational_logger_terminal_safety_case(void)
503 : : {
504 : : /*
505 : : * The payload mixes terminal-hostile bytes with ordinary UTF-8 text.
506 : : * Sanitization renders raw ESC bytes as printable text before formatted
507 : : * markup is interpreted
508 : : */
509 : : static const char payload[] =
510 : : "ascii"
511 : : "\001"
512 : : "del"
513 : : "\177"
514 : : "c1"
515 : : "\302\220"
516 : : "invalid"
517 : : "\303("
518 : : "overlong"
519 : : "\300\257"
520 : : "keep"
521 : : "\t\r\n"
522 : : "esc"
523 : : "\033[1m"
524 : : "utf8"
525 : : "°"
526 : : "à"
527 : : "Привет"
528 : : "天地玄黄宇宙洪荒日月盈昃辰宿列張"
529 : : "いろはにほへとちりぬるを"
530 : : "日本語漢字仮名交じり文"
531 : : "four-byte😀"
532 : : "isolated\200"
533 : : "surrogate\355\240\200"
534 : : "out-of-range\364\220\200\200"
535 : : "truncated\342\202";
536 : :
537 : 1 : rational_logger_mode = REGULAR;
538 : : /* Embedded NUL is part of the formatted payload and must not hide later
539 : : bytes. The final three bytes form an incomplete four-byte UTF-8 sequence */
540 : 1 : slog(REGULAR|UNDECOR,"%s%cafter-null\360\237\230",payload,'\0');
541 : 1 : rational_logger_mode = REGULAR;
542 : 1 : }
543 : :
544 : : /**
545 : : * @brief Capture one serp() stderr message
546 : : *
547 : : * @return Return describing success or failure
548 : : */
549 : 1 : static Return capture_librational_serp_case(void)
550 : : {
551 : 1 : INITTEST;
552 : :
553 : 1 : serp_case();
554 : :
555 : 1 : deliver(status);
556 : : }
557 : :
558 : : #ifndef EVIL_EMPIRE_OS
559 : : /**
560 : : * @brief Capture report() when formatting the final message fails
561 : : *
562 : : * @return Return describing success or failure
563 : : */
564 : 1 : static Return capture_librational_report_snprintf_failure(void)
565 : : {
566 : 1 : INITTEST;
567 : :
568 : : /* REPORT() should avoid writing a malformed message after snprintf failure */
569 : 1 : testmocking_snprintf_fail_next(1);
570 : 1 : report_single_case();
571 : 1 : testmocking_snprintf_disable();
572 : :
573 : 1 : deliver(status);
574 : : }
575 : :
576 : : /**
577 : : * @brief Capture report() when the primary write fails but fallback write works
578 : : *
579 : : * @return Return describing success or failure
580 : : */
581 : 1 : static Return capture_librational_report_write_fallback(void)
582 : : {
583 : 1 : INITTEST;
584 : :
585 : : /* The first write fails, so REPORT() should emit its fixed fallback text */
586 : 1 : testmocking_write_fail_next(1,EIO);
587 : 1 : report_single_case();
588 : 1 : testmocking_write_disable();
589 : :
590 : 1 : deliver(status);
591 : : }
592 : :
593 : : /**
594 : : * @brief Capture report() when both primary and fallback writes fail
595 : : *
596 : : * @return Return describing success or failure
597 : : */
598 : 1 : static Return capture_librational_report_write_full_failure(void)
599 : : {
600 : 1 : INITTEST;
601 : :
602 : : /* Both writes fail, so REPORT() must give up without leaking partial output */
603 : 1 : testmocking_write_fail_next(2,EIO);
604 : 1 : report_single_case();
605 : 1 : testmocking_write_disable();
606 : :
607 : 1 : deliver(status);
608 : : }
609 : : #endif
610 : :
611 : : /**
612 : : * @brief Check report() formatting with errno and source location
613 : : *
614 : : * @return Return describing success or failure
615 : : */
616 : 1 : static Return test_librational_0004_1(void)
617 : : {
618 : 1 : INITTEST;
619 : :
620 : : /* report() output goes to stderr and includes file, function, line, message, and errno */
621 : 1 : ASSERT(SUCCESS == match_function_output(
622 : : NULL,
623 : : expected_report_stderr_pattern,
624 : : capture_librational_report_test));
625 : :
626 : 1 : RETURN_STATUS;
627 : : }
628 : :
629 : : /**
630 : : * @brief Check slog() mode filtering, prefixes, and silent visibility
631 : : *
632 : : * @return Return describing success or failure
633 : : */
634 : 1 : static Return test_librational_0004_2(void)
635 : : {
636 : 1 : INITTEST;
637 : :
638 : : /* The pattern covers every printed line; variable fields are limited to time, path, and line */
639 : 1 : ASSERT(SUCCESS == match_function_output(
640 : : expected_slog_stdout_pattern,
641 : : NULL,
642 : : capture_librational_slog_test));
643 : :
644 : 1 : RETURN_STATUS;
645 : : }
646 : :
647 : : /**
648 : : * @brief Check serp() formatting with errno and source location
649 : : *
650 : : * @return Return describing success or failure
651 : : */
652 : 1 : static Return test_librational_0004_3(void)
653 : : {
654 : 1 : INITTEST;
655 : :
656 : : /* serp() writes directly to stderr without printf-style formatting */
657 : 1 : ASSERT(SUCCESS == match_function_output(
658 : : NULL,
659 : : expected_serp_stderr_pattern,
660 : : capture_librational_serp_case));
661 : :
662 : 1 : RETURN_STATUS;
663 : : }
664 : :
665 : : #ifndef EVIL_EMPIRE_OS
666 : : /**
667 : : * @brief Check that report() stays silent when message formatting fails
668 : : *
669 : : * @return Return describing success or failure
670 : : */
671 : 1 : static Return test_librational_0004_4(void)
672 : : {
673 : 1 : INITTEST;
674 : :
675 : : /* NULL patterns require both captured streams to stay empty */
676 : 1 : ASSERT(SUCCESS == match_function_output(
677 : : NULL,
678 : : NULL,
679 : : capture_librational_report_snprintf_failure));
680 : :
681 : 1 : RETURN_STATUS;
682 : : }
683 : :
684 : : /**
685 : : * @brief Check the exact buffer state after report() truncation
686 : : *
687 : : * @return Return describing success or failure
688 : : */
689 : 1 : static Return test_librational_0004_5(void)
690 : : {
691 : 1 : INITTEST;
692 : :
693 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
694 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
695 : :
696 : : /* This test inspects raw buffer lengths, so it uses function_capture() directly */
697 : 1 : ASSERT(SUCCESS == function_capture(
698 : : report_truncated_case,
699 : : captured_stdout,
700 : : captured_stderr));
701 : :
702 : 1 : ASSERT(captured_stdout->length == 0U);
703 : 1 : ASSERT(captured_stderr->length == MAX_CHARACTERS + 1U);
704 : 1 : ASSERT(captured_stderr->string_length == MAX_CHARACTERS);
705 : :
706 : 1 : const char *stderr_data = m_data_ro(char,captured_stderr);
707 : 1 : ASSERT(stderr_data != NULL);
708 : :
709 : 1 : IF(stderr_data != NULL)
710 : : {
711 : 1 : const char expected_tail[MAX_CHARACTERS] = {0};
712 : :
713 : : /* The snprintf mock writes one visible byte and leaves the rest zero-filled */
714 : 1 : ASSERT(stderr_data[0] == 'T');
715 : 1 : ASSERT(0 == memcmp(stderr_data + 1,expected_tail,MAX_CHARACTERS));
716 : : }
717 : :
718 : 1 : call(m_del(captured_stdout));
719 : 1 : call(m_del(captured_stderr));
720 : :
721 : 1 : RETURN_STATUS;
722 : : }
723 : :
724 : : /**
725 : : * @brief Check the report() fallback message after primary write failure
726 : : *
727 : : * @return Return describing success or failure
728 : : */
729 : 1 : static Return test_librational_0004_6(void)
730 : : {
731 : 1 : INITTEST;
732 : :
733 : 1 : ASSERT(SUCCESS == match_function_output(
734 : : NULL,
735 : : expected_write_fallback_stderr_pattern,
736 : : capture_librational_report_write_fallback));
737 : :
738 : 1 : RETURN_STATUS;
739 : : }
740 : :
741 : : /**
742 : : * @brief Check that report() stays silent when both writes fail
743 : : *
744 : : * @return Return describing success or failure
745 : : */
746 : 1 : static Return test_librational_0004_7(void)
747 : : {
748 : 1 : INITTEST;
749 : :
750 : 1 : ASSERT(SUCCESS == match_function_output(
751 : : NULL,
752 : : NULL,
753 : : capture_librational_report_write_full_failure));
754 : :
755 : 1 : RETURN_STATUS;
756 : : }
757 : : #endif
758 : :
759 : : /**
760 : : * @brief Check rational_reconvert() edge cases around empty and REMEMBER modes
761 : : *
762 : : * @return Return describing success or failure
763 : : */
764 : 1 : static Return test_librational_0004_8(void)
765 : : {
766 : 1 : INITTEST;
767 : :
768 : 1 : ASSERT(0 == strcmp(rational_reconvert(0),""));
769 : 1 : ASSERT(0 == strcmp(rational_reconvert(REMEMBER),"REMEMBER"));
770 : 1 : ASSERT(0 == strcmp(
771 : : rational_reconvert(REGULAR|REMEMBER|VISIBLE_IN_SILENT),
772 : : "REGULAR | REMEMBER | VISIBLE_IN_SILENT"));
773 : :
774 : 1 : RETURN_STATUS;
775 : : }
776 : :
777 : : /**
778 : : * @brief Check rational_reconvert() names every logger mode flag
779 : : *
780 : : * @return Return describing success or failure
781 : : */
782 : 1 : static Return test_librational_0004_9(void)
783 : : {
784 : 1 : INITTEST;
785 : :
786 : : /* Pin all single-flag names and the full mapping order used in diagnostics */
787 : 1 : ASSERT(0 == strcmp(rational_reconvert(REGULAR),"REGULAR"));
788 : 1 : ASSERT(0 == strcmp(rational_reconvert(VERBOSE),"VERBOSE"));
789 : 1 : ASSERT(0 == strcmp(rational_reconvert(TESTING),"TESTING"));
790 : 1 : ASSERT(0 == strcmp(rational_reconvert(ERROR),"ERROR"));
791 : 1 : ASSERT(0 == strcmp(rational_reconvert(SILENT),"SILENT"));
792 : 1 : ASSERT(0 == strcmp(rational_reconvert(UNDECOR),"UNDECOR"));
793 : 1 : ASSERT(0 == strcmp(rational_reconvert(REMEMBER),"REMEMBER"));
794 : 1 : ASSERT(0 == strcmp(rational_reconvert(VISIBLE_IN_SILENT),"VISIBLE_IN_SILENT"));
795 : 1 : ASSERT(0 == strcmp(
796 : : rational_reconvert(REGULAR|VERBOSE|TESTING|ERROR|SILENT|UNDECOR|REMEMBER|VISIBLE_IN_SILENT),
797 : : "REGULAR | VERBOSE | TESTING | ERROR | SILENT | UNDECOR | REMEMBER | VISIBLE_IN_SILENT"));
798 : :
799 : 1 : RETURN_STATUS;
800 : : }
801 : :
802 : : #ifndef EVIL_EMPIRE_OS
803 : : /**
804 : : * @brief Check that slog() fails closed when line allocation or sanitization fails
805 : : *
806 : : * @return Return describing success or failure
807 : : */
808 : 1 : static Return test_librational_0004_10(void)
809 : : {
810 : 1 : INITTEST;
811 : :
812 : 1 : ASSERT(SUCCESS == match_function_output(
813 : : NULL,
814 : : NULL,
815 : : capture_librational_logger_allocation_failures));
816 : :
817 : 1 : RETURN_STATUS;
818 : : }
819 : :
820 : : /**
821 : : * @brief Check slog() output when timestamp helper calls fail
822 : : *
823 : : * @return Return describing success or failure
824 : : */
825 : 1 : static Return test_librational_0004_11(void)
826 : : {
827 : 1 : INITTEST;
828 : :
829 : 1 : ASSERT(SUCCESS == match_function_output(
830 : : expected_logger_time_failure_stdout_pattern,
831 : : NULL,
832 : : capture_librational_logger_time_failures));
833 : :
834 : 1 : RETURN_STATUS;
835 : : }
836 : :
837 : : /**
838 : : * @brief Check that slog() stays silent when vsnprintf fails
839 : : *
840 : : * @return Return describing success or failure
841 : : */
842 : 1 : static Return test_librational_0004_12(void)
843 : : {
844 : 1 : INITTEST;
845 : :
846 : 1 : ASSERT(SUCCESS == match_function_output(
847 : : NULL,
848 : : NULL,
849 : : capture_librational_logger_vsnprintf_failure));
850 : :
851 : 1 : RETURN_STATUS;
852 : : }
853 : : #endif
854 : :
855 : : /**
856 : : * @brief Check that slog(ERROR) stays silent when no mode accepts ERROR
857 : : *
858 : : * @return Return describing success or failure
859 : : */
860 : 1 : static Return test_librational_0004_13(void)
861 : : {
862 : 1 : INITTEST;
863 : :
864 : 1 : ASSERT(SUCCESS == match_function_output(
865 : : NULL,
866 : : NULL,
867 : : capture_librational_logger_disabled_error_mode));
868 : :
869 : 1 : RETURN_STATUS;
870 : : }
871 : :
872 : : #ifndef EVIL_EMPIRE_OS
873 : : /**
874 : : * @brief Check that REMEMBER has no output when no payload is available
875 : : *
876 : : * @return Return describing success or failure
877 : : */
878 : 1 : static Return test_librational_0004_14(void)
879 : : {
880 : 1 : INITTEST;
881 : :
882 : 1 : ASSERT(SUCCESS == match_function_output(
883 : : NULL,
884 : : NULL,
885 : : capture_librational_logger_remember_without_payload));
886 : :
887 : 1 : RETURN_STATUS;
888 : : }
889 : : #endif
890 : :
891 : : /**
892 : : * @brief Check that slog() escapes terminal-hostile text without damaging normal UTF-8
893 : : *
894 : : * @return Return describing success or failure
895 : : */
896 : 1 : static Return test_librational_0004_15(void)
897 : : {
898 : 1 : INITTEST;
899 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
900 : :
901 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
902 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
903 : :
904 : : static const char expected_stdout[] =
905 : : "ascii"
906 : : "\\x01"
907 : : "del"
908 : : "\\x7F"
909 : : "c1"
910 : : "\\xC2\\x90"
911 : : "invalid"
912 : : "\\xC3("
913 : : "overlong"
914 : : "\\xC0\\xAF"
915 : : "keep"
916 : : "\t\r\n"
917 : : "esc"
918 : : "\\x1B[1m"
919 : : "utf8"
920 : : "°"
921 : : "à"
922 : : "Привет"
923 : : "天地玄黄宇宙洪荒日月盈昃辰宿列張"
924 : : "いろはにほへとちりぬるを"
925 : : "日本語漢字仮名交じり文"
926 : : "four-byte😀"
927 : : "isolated\\x80"
928 : : "surrogate\\xED\\xA0\\x80"
929 : : "out-of-range\\xF4\\x90\\x80\\x80"
930 : : "truncated\\xE2\\x82"
931 : : "\\x00after-null\\xF0\\x9F\\x98";
932 : :
933 : 1 : rational_color_mode = COLOR_MODE_NEVER;
934 : 1 : ASSERT(SUCCESS == function_capture(
935 : : librational_logger_terminal_safety_case,
936 : : captured_stdout,
937 : : captured_stderr));
938 : :
939 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_stdout));
940 : 1 : ASSERT(captured_stderr->length == 0U);
941 : :
942 : 1 : rational_color_mode = initial_color_mode;
943 : 1 : call(m_del(captured_stdout));
944 : 1 : call(m_del(captured_stderr));
945 : :
946 : 1 : RETURN_STATUS;
947 : : }
948 : :
949 : : #ifndef EVIL_EMPIRE_OS
950 : : /**
951 : : * @brief Check that a failed prefix stops processing the rest of its message
952 : : *
953 : : * @return Return describing success or failure
954 : : */
955 : 1 : static Return test_librational_0004_17(void)
956 : : {
957 : 1 : INITTEST;
958 : :
959 : 1 : ASSERT(SUCCESS == match_function_output(
960 : : NULL,
961 : : NULL,
962 : : capture_librational_logger_prefix_failures));
963 : :
964 : 1 : RETURN_STATUS;
965 : : }
966 : : #endif
967 : :
968 : : /**
969 : : * @brief Check that function_capture() flushes pending stdout before redirection
970 : : *
971 : : * @return Return describing success or failure
972 : : */
973 : 1 : static Return test_librational_0004_capture_flush(void)
974 : : {
975 : 1 : INITTEST;
976 : :
977 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
978 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
979 : 1 : int saved_stdout_fd = -1;
980 : 1 : FILE *stdout_tmp = NULL;
981 : :
982 : 1 : saved_stdout_fd = dup(STDOUT_FILENO);
983 : 1 : ASSERT(saved_stdout_fd != -1);
984 : :
985 : : /* Redirect stdout away from the terminal, then leave text pending in stdio */
986 [ + - ]: 1 : if(SUCCESS == status)
987 : : {
988 : 1 : stdout_tmp = tmpfile();
989 : : }
990 : :
991 : 1 : ASSERT(stdout_tmp != NULL);
992 : 1 : ASSERT(0 == fflush(stdout));
993 : 1 : ASSERT(dup2(fileno(stdout_tmp),STDOUT_FILENO) != -1);
994 : 1 : ASSERT(fprintf(stdout,"pending stdout before function_capture") > 0);
995 : :
996 : : /* function_capture() should flush that pending text before installing its own redirection */
997 : 1 : ASSERT(SUCCESS == function_capture(no_output_test,captured_stdout,captured_stderr));
998 : 1 : ASSERT(captured_stdout->length == 0);
999 : 1 : ASSERT(captured_stderr->length == 0);
1000 : :
1001 : 1 : IF(stdout_tmp != NULL)
1002 : : {
1003 : 1 : (void)fflush(stdout);
1004 : : }
1005 : :
1006 [ + - ]: 1 : if(saved_stdout_fd != -1)
1007 : : {
1008 [ - + ]: 1 : if(dup2(saved_stdout_fd,STDOUT_FILENO) == -1)
1009 : : {
1010 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
1011 : 0 : status = FAILURE;
1012 : : }
1013 : :
1014 : 1 : (void)close(saved_stdout_fd);
1015 : : }
1016 : :
1017 : 1 : IF(stdout_tmp != NULL)
1018 : : {
1019 : 1 : (void)fclose(stdout_tmp);
1020 : : }
1021 : :
1022 : 1 : call(m_del(captured_stdout));
1023 : 1 : call(m_del(captured_stderr));
1024 : :
1025 : 1 : RETURN_STATUS;
1026 : : }
1027 : :
1028 : : /**
1029 : : * @brief Exercise report(), serp(), slog(), and logger mode conversion
1030 : : *
1031 : : * The suite checks formatted report output, plain errno reporting through serp(),
1032 : : * slog() mode filtering and decoration, logger conversion helpers, failure paths
1033 : : * for formatting/allocation/time/write helpers, and the output-capture guard that
1034 : : * flushes pending stdout before redirecting streams
1035 : : *
1036 : : * @return SUCCESS when all subtests pass
1037 : : */
1038 : 1 : Return test_librational_0004(void)
1039 : : {
1040 : 1 : INITTEST;
1041 : :
1042 : 1 : TEST(test_librational_0004_capture_flush,"function_capture() flushes pending stdout before redirection");
1043 : 1 : TEST(test_librational_0004_1,"report() writes formatted errno messages with source location");
1044 : 1 : TEST(test_librational_0004_2,"slog() honors logger modes, decorations and silent visibility");
1045 : 1 : TEST(test_librational_0004_3,"serp() writes errno, source file and function name");
1046 : : #ifndef EVIL_EMPIRE_OS
1047 : 1 : TEST(test_librational_0004_4,"report() stays silent when final message formatting fails");
1048 : 1 : TEST(test_librational_0004_5,"report() keeps a truncated fixed-size message buffer terminated");
1049 : 1 : TEST(test_librational_0004_6,"report() writes fallback text after primary write failure");
1050 : 1 : TEST(test_librational_0004_7,"report() stays silent when primary and fallback writes fail");
1051 : : #endif
1052 : 1 : TEST(test_librational_0004_8,"rational_reconvert() covers empty and REMEMBER edge cases");
1053 : 1 : TEST(test_librational_0004_9,"rational_reconvert() names every logger flag");
1054 : : #ifndef EVIL_EMPIRE_OS
1055 : 1 : TEST(test_librational_0004_10,"slog() fails closed when line allocation or sanitization fails");
1056 : 1 : TEST(test_librational_0004_11,"slog() keeps payloads visible when time formatting fails");
1057 : 1 : TEST(test_librational_0004_12,"slog() stays silent when vsnprintf fails");
1058 : : #endif
1059 : 1 : TEST(test_librational_0004_13,"slog(ERROR) stays silent when no mode accepts ERROR");
1060 : : #ifndef EVIL_EMPIRE_OS
1061 : 1 : TEST(test_librational_0004_14,"slog(REMEMBER) skips empty or unformatted payloads");
1062 : : #endif
1063 : 1 : TEST(test_librational_0004_15,"slog() escapes unsafe terminal bytes while preserving normal UTF-8");
1064 : : #ifndef EVIL_EMPIRE_OS
1065 : 1 : TEST(test_librational_0004_17,"slog() stops formatting and allocation after a prefix failure");
1066 : : #endif
1067 : :
1068 : 1 : RETURN_STATUS;
1069 : : }
|