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 logger line allocation fails
303 : : *
304 : : * @return Return describing success or failure
305 : : */
306 : 1 : static Return capture_librational_logger_realloc_failure(void)
307 : : {
308 : 1 : INITTEST;
309 : :
310 : : /* The logger should stay silent when it cannot grow the output line */
311 : 1 : testmocking_realloc_fail_next(1);
312 : 1 : rational_logger_mode = REGULAR;
313 : 1 : slog(REGULAR|UNDECOR,"hidden");
314 : 1 : rational_logger_mode = REGULAR;
315 : 1 : testmocking_realloc_disable();
316 : :
317 : 1 : deliver(status);
318 : : }
319 : :
320 : : /**
321 : : * @brief Capture slog() output from timestamp failure scenarios
322 : : *
323 : : * @return Return describing success or failure
324 : : */
325 : 1 : static Return capture_librational_logger_time_failures(void)
326 : : {
327 : 1 : INITTEST;
328 : :
329 : : /* Cleanup disables every mock in case the captured scenario exits early */
330 : 1 : logger_time_failure_cases();
331 : 1 : rational_logger_mode = REGULAR;
332 : 1 : testmocking_gettimeofday_disable();
333 : 1 : testmocking_localtime_r_disable();
334 : 1 : testmocking_snprintf_disable();
335 : 1 : testmocking_write_disable();
336 : :
337 : 1 : deliver(status);
338 : : }
339 : :
340 : : /**
341 : : * @brief Capture slog() output when vsnprintf fails before line allocation
342 : : *
343 : : * @return Return describing success or failure
344 : : */
345 : 1 : static Return capture_librational_logger_vsnprintf_failure(void)
346 : : {
347 : 1 : INITTEST;
348 : :
349 : : /* A failed payload size calculation should suppress the incomplete line */
350 : 1 : testmocking_vsnprintf_fail_next(1);
351 : 1 : rational_logger_mode = REGULAR;
352 : 1 : slog(REGULAR|UNDECOR,"hidden");
353 : 1 : rational_logger_mode = REGULAR;
354 : 1 : testmocking_vsnprintf_disable();
355 : :
356 : 1 : deliver(status);
357 : : }
358 : : #endif
359 : :
360 : : /**
361 : : * @brief Capture an ERROR log while no logger mode accepts it
362 : : *
363 : : * @return Return describing success or failure
364 : : */
365 : 1 : static Return capture_librational_logger_disabled_error_mode(void)
366 : : {
367 : 1 : INITTEST;
368 : :
369 : : /* With no active mode bits, even ERROR must stay silent */
370 : 1 : rational_logger_mode = (LOGMODES)0;
371 : 1 : slog(ERROR,"hidden");
372 : 1 : rational_logger_mode = REGULAR;
373 : :
374 : 1 : deliver(status);
375 : : }
376 : :
377 : : #ifndef EVIL_EMPIRE_OS
378 : : /**
379 : : * @brief Capture REMEMBER calls that have no usable payload
380 : : *
381 : : * @return Return describing success or failure
382 : : */
383 : 1 : static Return capture_librational_logger_remember_without_payload(void)
384 : : {
385 : 1 : INITTEST;
386 : :
387 : : /* Empty payload produces no remembered or printed line */
388 : 1 : rational_logger_mode = REGULAR;
389 : 1 : slog(REGULAR|UNDECOR|REMEMBER,"");
390 : :
391 : : /* Failed formatting also leaves REMEMBER without a payload to deliver */
392 : 1 : testmocking_vsnprintf_fail_next(1);
393 : 1 : slog(REGULAR|UNDECOR|REMEMBER,"hidden");
394 : 1 : testmocking_vsnprintf_disable();
395 : :
396 : 1 : rational_logger_mode = REGULAR;
397 : :
398 : 1 : deliver(status);
399 : : }
400 : : #endif
401 : :
402 : : /**
403 : : * @brief Capture the basic report() stderr contract
404 : : *
405 : : * @return Return describing success or failure
406 : : */
407 : 1 : static Return capture_librational_report_test(void)
408 : : {
409 : 1 : INITTEST;
410 : :
411 : 1 : report_test();
412 : :
413 : 1 : deliver(status);
414 : : }
415 : :
416 : : /**
417 : : * @brief Capture the full slog() mode matrix
418 : : *
419 : : * @return Return describing success or failure
420 : : */
421 : 1 : static Return capture_librational_slog_test(void)
422 : : {
423 : 1 : INITTEST;
424 : :
425 : : /* Reset the global logger mode after the matrix leaves it in SILENT */
426 : 1 : slog_test();
427 : 1 : rational_logger_mode = REGULAR;
428 : :
429 : 1 : deliver(status);
430 : : }
431 : :
432 : : /**
433 : : * @brief Capture a log line that contains unsafe terminal bytes
434 : : */
435 : 1 : static void librational_logger_terminal_safety_case(void)
436 : : {
437 : : /*
438 : : * The payload mixes terminal-hostile bytes with ordinary UTF-8 text.
439 : : * It deliberately keeps raw ESC because this first sanitizer pass preserves
440 : : * existing logger decorations until an explicit decoration whitelist exists
441 : : */
442 : : static const char payload[] =
443 : : "ascii"
444 : : "\001"
445 : : "del"
446 : : "\177"
447 : : "c1"
448 : : "\302\220"
449 : : "invalid"
450 : : "\303("
451 : : "overlong"
452 : : "\300\257"
453 : : "keep"
454 : : "\t\r\n"
455 : : "esc"
456 : : "\033[1m"
457 : : "utf8"
458 : : "°"
459 : : "à"
460 : : "Привет"
461 : : "天地玄黄宇宙洪荒日月盈昃辰宿列張"
462 : : "いろはにほへとちりぬるを"
463 : : "日本語漢字仮名交じり文";
464 : :
465 : 1 : rational_logger_mode = REGULAR;
466 : 1 : slog(REGULAR|UNDECOR,"%s",payload);
467 : 1 : rational_logger_mode = REGULAR;
468 : 1 : }
469 : :
470 : : /**
471 : : * @brief Capture one serp() stderr message
472 : : *
473 : : * @return Return describing success or failure
474 : : */
475 : 1 : static Return capture_librational_serp_case(void)
476 : : {
477 : 1 : INITTEST;
478 : :
479 : 1 : serp_case();
480 : :
481 : 1 : deliver(status);
482 : : }
483 : :
484 : : #ifndef EVIL_EMPIRE_OS
485 : : /**
486 : : * @brief Capture report() when formatting the final message fails
487 : : *
488 : : * @return Return describing success or failure
489 : : */
490 : 1 : static Return capture_librational_report_snprintf_failure(void)
491 : : {
492 : 1 : INITTEST;
493 : :
494 : : /* REPORT() should avoid writing a malformed message after snprintf failure */
495 : 1 : testmocking_snprintf_fail_next(1);
496 : 1 : report_single_case();
497 : 1 : testmocking_snprintf_disable();
498 : :
499 : 1 : deliver(status);
500 : : }
501 : :
502 : : /**
503 : : * @brief Capture report() when the primary write fails but fallback write works
504 : : *
505 : : * @return Return describing success or failure
506 : : */
507 : 1 : static Return capture_librational_report_write_fallback(void)
508 : : {
509 : 1 : INITTEST;
510 : :
511 : : /* The first write fails, so REPORT() should emit its fixed fallback text */
512 : 1 : testmocking_write_fail_next(1,EIO);
513 : 1 : report_single_case();
514 : 1 : testmocking_write_disable();
515 : :
516 : 1 : deliver(status);
517 : : }
518 : :
519 : : /**
520 : : * @brief Capture report() when both primary and fallback writes fail
521 : : *
522 : : * @return Return describing success or failure
523 : : */
524 : 1 : static Return capture_librational_report_write_full_failure(void)
525 : : {
526 : 1 : INITTEST;
527 : :
528 : : /* Both writes fail, so REPORT() must give up without leaking partial output */
529 : 1 : testmocking_write_fail_next(2,EIO);
530 : 1 : report_single_case();
531 : 1 : testmocking_write_disable();
532 : :
533 : 1 : deliver(status);
534 : : }
535 : : #endif
536 : :
537 : : /**
538 : : * @brief Check report() formatting with errno and source location
539 : : *
540 : : * @return Return describing success or failure
541 : : */
542 : 1 : static Return test_librational_0004_1(void)
543 : : {
544 : 1 : INITTEST;
545 : :
546 : : /* report() output goes to stderr and includes file, function, line, message, and errno */
547 : 1 : ASSERT(SUCCESS == match_function_output(
548 : : NULL,
549 : : expected_report_stderr_pattern,
550 : : capture_librational_report_test));
551 : :
552 : 1 : RETURN_STATUS;
553 : : }
554 : :
555 : : /**
556 : : * @brief Check slog() mode filtering, prefixes, and silent visibility
557 : : *
558 : : * @return Return describing success or failure
559 : : */
560 : 1 : static Return test_librational_0004_2(void)
561 : : {
562 : 1 : INITTEST;
563 : :
564 : : /* The pattern covers every printed line; variable fields are limited to time, path, and line */
565 : 1 : ASSERT(SUCCESS == match_function_output(
566 : : expected_slog_stdout_pattern,
567 : : NULL,
568 : : capture_librational_slog_test));
569 : :
570 : 1 : RETURN_STATUS;
571 : : }
572 : :
573 : : /**
574 : : * @brief Check serp() formatting with errno and source location
575 : : *
576 : : * @return Return describing success or failure
577 : : */
578 : 1 : static Return test_librational_0004_3(void)
579 : : {
580 : 1 : INITTEST;
581 : :
582 : : /* serp() writes directly to stderr without printf-style formatting */
583 : 1 : ASSERT(SUCCESS == match_function_output(
584 : : NULL,
585 : : expected_serp_stderr_pattern,
586 : : capture_librational_serp_case));
587 : :
588 : 1 : RETURN_STATUS;
589 : : }
590 : :
591 : : #ifndef EVIL_EMPIRE_OS
592 : : /**
593 : : * @brief Check that report() stays silent when message formatting fails
594 : : *
595 : : * @return Return describing success or failure
596 : : */
597 : 1 : static Return test_librational_0004_4(void)
598 : : {
599 : 1 : INITTEST;
600 : :
601 : : /* NULL patterns require both captured streams to stay empty */
602 : 1 : ASSERT(SUCCESS == match_function_output(
603 : : NULL,
604 : : NULL,
605 : : capture_librational_report_snprintf_failure));
606 : :
607 : 1 : RETURN_STATUS;
608 : : }
609 : :
610 : : /**
611 : : * @brief Check the exact buffer state after report() truncation
612 : : *
613 : : * @return Return describing success or failure
614 : : */
615 : 1 : static Return test_librational_0004_5(void)
616 : : {
617 : 1 : INITTEST;
618 : :
619 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
620 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
621 : :
622 : : /* This test inspects raw buffer lengths, so it uses function_capture() directly */
623 : 1 : ASSERT(SUCCESS == function_capture(
624 : : report_truncated_case,
625 : : captured_stdout,
626 : : captured_stderr));
627 : :
628 : 1 : ASSERT(captured_stdout->length == 0U);
629 : 1 : ASSERT(captured_stderr->length == MAX_CHARACTERS + 1U);
630 : 1 : ASSERT(captured_stderr->string_length == MAX_CHARACTERS);
631 : :
632 : 1 : const char *stderr_data = m_data_ro(char,captured_stderr);
633 : 1 : ASSERT(stderr_data != NULL);
634 : :
635 : 1 : IF(stderr_data != NULL)
636 : : {
637 : 1 : const char expected_tail[MAX_CHARACTERS] = {0};
638 : :
639 : : /* The snprintf mock writes one visible byte and leaves the rest zero-filled */
640 : 1 : ASSERT(stderr_data[0] == 'T');
641 : 1 : ASSERT(0 == memcmp(stderr_data + 1,expected_tail,MAX_CHARACTERS));
642 : : }
643 : :
644 : 1 : call(m_del(captured_stdout));
645 : 1 : call(m_del(captured_stderr));
646 : :
647 : 1 : RETURN_STATUS;
648 : : }
649 : :
650 : : /**
651 : : * @brief Check the report() fallback message after primary write failure
652 : : *
653 : : * @return Return describing success or failure
654 : : */
655 : 1 : static Return test_librational_0004_6(void)
656 : : {
657 : 1 : INITTEST;
658 : :
659 : 1 : ASSERT(SUCCESS == match_function_output(
660 : : NULL,
661 : : expected_write_fallback_stderr_pattern,
662 : : capture_librational_report_write_fallback));
663 : :
664 : 1 : RETURN_STATUS;
665 : : }
666 : :
667 : : /**
668 : : * @brief Check that report() stays silent when both writes fail
669 : : *
670 : : * @return Return describing success or failure
671 : : */
672 : 1 : static Return test_librational_0004_7(void)
673 : : {
674 : 1 : INITTEST;
675 : :
676 : 1 : ASSERT(SUCCESS == match_function_output(
677 : : NULL,
678 : : NULL,
679 : : capture_librational_report_write_full_failure));
680 : :
681 : 1 : RETURN_STATUS;
682 : : }
683 : : #endif
684 : :
685 : : /**
686 : : * @brief Check rational_reconvert() edge cases around empty and REMEMBER modes
687 : : *
688 : : * @return Return describing success or failure
689 : : */
690 : 1 : static Return test_librational_0004_8(void)
691 : : {
692 : 1 : INITTEST;
693 : :
694 : 1 : ASSERT(0 == strcmp(rational_reconvert(0),""));
695 : 1 : ASSERT(0 == strcmp(rational_reconvert(REMEMBER),"REMEMBER"));
696 : 1 : ASSERT(0 == strcmp(
697 : : rational_reconvert(REGULAR|REMEMBER|VISIBLE_IN_SILENT),
698 : : "REGULAR | REMEMBER | VISIBLE_IN_SILENT"));
699 : :
700 : 1 : RETURN_STATUS;
701 : : }
702 : :
703 : : /**
704 : : * @brief Check rational_reconvert() names every logger mode flag
705 : : *
706 : : * @return Return describing success or failure
707 : : */
708 : 1 : static Return test_librational_0004_9(void)
709 : : {
710 : 1 : INITTEST;
711 : :
712 : : /* Pin all single-flag names and the full mapping order used in diagnostics */
713 : 1 : ASSERT(0 == strcmp(rational_reconvert(REGULAR),"REGULAR"));
714 : 1 : ASSERT(0 == strcmp(rational_reconvert(VERBOSE),"VERBOSE"));
715 : 1 : ASSERT(0 == strcmp(rational_reconvert(TESTING),"TESTING"));
716 : 1 : ASSERT(0 == strcmp(rational_reconvert(ERROR),"ERROR"));
717 : 1 : ASSERT(0 == strcmp(rational_reconvert(SILENT),"SILENT"));
718 : 1 : ASSERT(0 == strcmp(rational_reconvert(UNDECOR),"UNDECOR"));
719 : 1 : ASSERT(0 == strcmp(rational_reconvert(REMEMBER),"REMEMBER"));
720 : 1 : ASSERT(0 == strcmp(rational_reconvert(VISIBLE_IN_SILENT),"VISIBLE_IN_SILENT"));
721 : 1 : ASSERT(0 == strcmp(
722 : : rational_reconvert(REGULAR|VERBOSE|TESTING|ERROR|SILENT|UNDECOR|REMEMBER|VISIBLE_IN_SILENT),
723 : : "REGULAR | VERBOSE | TESTING | ERROR | SILENT | UNDECOR | REMEMBER | VISIBLE_IN_SILENT"));
724 : :
725 : 1 : RETURN_STATUS;
726 : : }
727 : :
728 : : #ifndef EVIL_EMPIRE_OS
729 : : /**
730 : : * @brief Check that slog() stays silent when logger line allocation fails
731 : : *
732 : : * @return Return describing success or failure
733 : : */
734 : 1 : static Return test_librational_0004_10(void)
735 : : {
736 : 1 : INITTEST;
737 : :
738 : 1 : ASSERT(SUCCESS == match_function_output(
739 : : NULL,
740 : : NULL,
741 : : capture_librational_logger_realloc_failure));
742 : :
743 : 1 : RETURN_STATUS;
744 : : }
745 : :
746 : : /**
747 : : * @brief Check slog() output when timestamp helper calls fail
748 : : *
749 : : * @return Return describing success or failure
750 : : */
751 : 1 : static Return test_librational_0004_11(void)
752 : : {
753 : 1 : INITTEST;
754 : :
755 : 1 : ASSERT(SUCCESS == match_function_output(
756 : : expected_logger_time_failure_stdout_pattern,
757 : : NULL,
758 : : capture_librational_logger_time_failures));
759 : :
760 : 1 : RETURN_STATUS;
761 : : }
762 : :
763 : : /**
764 : : * @brief Check that slog() stays silent when vsnprintf fails
765 : : *
766 : : * @return Return describing success or failure
767 : : */
768 : 1 : static Return test_librational_0004_12(void)
769 : : {
770 : 1 : INITTEST;
771 : :
772 : 1 : ASSERT(SUCCESS == match_function_output(
773 : : NULL,
774 : : NULL,
775 : : capture_librational_logger_vsnprintf_failure));
776 : :
777 : 1 : RETURN_STATUS;
778 : : }
779 : : #endif
780 : :
781 : : /**
782 : : * @brief Check that slog(ERROR) stays silent when no mode accepts ERROR
783 : : *
784 : : * @return Return describing success or failure
785 : : */
786 : 1 : static Return test_librational_0004_13(void)
787 : : {
788 : 1 : INITTEST;
789 : :
790 : 1 : ASSERT(SUCCESS == match_function_output(
791 : : NULL,
792 : : NULL,
793 : : capture_librational_logger_disabled_error_mode));
794 : :
795 : 1 : RETURN_STATUS;
796 : : }
797 : :
798 : : #ifndef EVIL_EMPIRE_OS
799 : : /**
800 : : * @brief Check that REMEMBER has no output when no payload is available
801 : : *
802 : : * @return Return describing success or failure
803 : : */
804 : 1 : static Return test_librational_0004_14(void)
805 : : {
806 : 1 : INITTEST;
807 : :
808 : 1 : ASSERT(SUCCESS == match_function_output(
809 : : NULL,
810 : : NULL,
811 : : capture_librational_logger_remember_without_payload));
812 : :
813 : 1 : RETURN_STATUS;
814 : : }
815 : : #endif
816 : :
817 : : /**
818 : : * @brief Check that slog() escapes terminal-hostile text without damaging normal UTF-8
819 : : *
820 : : * @return Return describing success or failure
821 : : */
822 : 1 : static Return test_librational_0004_15(void)
823 : : {
824 : 1 : INITTEST;
825 : :
826 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
827 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
828 : :
829 : : static const char expected_stdout[] =
830 : : "ascii"
831 : : "\\x01"
832 : : "del"
833 : : "\\x7F"
834 : : "c1"
835 : : "\\xC2\\x90"
836 : : "invalid"
837 : : "\\xC3("
838 : : "overlong"
839 : : "\\xC0\\xAF"
840 : : "keep"
841 : : "\t\r\n"
842 : : "esc"
843 : : "\033[1m"
844 : : "utf8"
845 : : "°"
846 : : "à"
847 : : "Привет"
848 : : "天地玄黄宇宙洪荒日月盈昃辰宿列張"
849 : : "いろはにほへとちりぬるを"
850 : : "日本語漢字仮名交じり文";
851 : :
852 : 1 : ASSERT(SUCCESS == function_capture(
853 : : librational_logger_terminal_safety_case,
854 : : captured_stdout,
855 : : captured_stderr));
856 : :
857 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_stdout));
858 : 1 : ASSERT(captured_stderr->length == 0U);
859 : :
860 : 1 : call(m_del(captured_stdout));
861 : 1 : call(m_del(captured_stderr));
862 : :
863 : 1 : RETURN_STATUS;
864 : : }
865 : :
866 : : /**
867 : : * @brief Check that function_capture() flushes pending stdout before redirection
868 : : *
869 : : * @return Return describing success or failure
870 : : */
871 : 1 : static Return test_librational_0004_capture_flush(void)
872 : : {
873 : 1 : INITTEST;
874 : :
875 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
876 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
877 : 1 : int saved_stdout_fd = -1;
878 : 1 : FILE *stdout_tmp = NULL;
879 : :
880 : 1 : saved_stdout_fd = dup(STDOUT_FILENO);
881 : 1 : ASSERT(saved_stdout_fd != -1);
882 : :
883 : : /* Redirect stdout away from the terminal, then leave text pending in stdio */
884 [ + - ]: 1 : if(SUCCESS == status)
885 : : {
886 : 1 : stdout_tmp = tmpfile();
887 : : }
888 : :
889 : 1 : ASSERT(stdout_tmp != NULL);
890 : 1 : ASSERT(0 == fflush(stdout));
891 : 1 : ASSERT(dup2(fileno(stdout_tmp),STDOUT_FILENO) != -1);
892 : 1 : ASSERT(fprintf(stdout,"pending stdout before function_capture") > 0);
893 : :
894 : : /* function_capture() should flush that pending text before installing its own redirection */
895 : 1 : ASSERT(SUCCESS == function_capture(no_output_test,captured_stdout,captured_stderr));
896 : 1 : ASSERT(captured_stdout->length == 0);
897 : 1 : ASSERT(captured_stderr->length == 0);
898 : :
899 : 1 : IF(stdout_tmp != NULL)
900 : : {
901 : 1 : (void)fflush(stdout);
902 : : }
903 : :
904 [ + - ]: 1 : if(saved_stdout_fd != -1)
905 : : {
906 [ - + ]: 1 : if(dup2(saved_stdout_fd,STDOUT_FILENO) == -1)
907 : : {
908 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
909 : 0 : status = FAILURE;
910 : : }
911 : :
912 : 1 : (void)close(saved_stdout_fd);
913 : : }
914 : :
915 : 1 : IF(stdout_tmp != NULL)
916 : : {
917 : 1 : (void)fclose(stdout_tmp);
918 : : }
919 : :
920 : 1 : call(m_del(captured_stdout));
921 : 1 : call(m_del(captured_stderr));
922 : :
923 : 1 : RETURN_STATUS;
924 : : }
925 : :
926 : : /**
927 : : * @brief Exercise report(), serp(), slog(), and logger mode conversion
928 : : *
929 : : * The suite checks formatted report output, plain errno reporting through serp(),
930 : : * slog() mode filtering and decoration, logger conversion helpers, failure paths
931 : : * for formatting/allocation/time/write helpers, and the output-capture guard that
932 : : * flushes pending stdout before redirecting streams
933 : : *
934 : : * @return SUCCESS when all subtests pass
935 : : */
936 : 1 : Return test_librational_0004(void)
937 : : {
938 : 1 : INITTEST;
939 : :
940 : 1 : TEST(test_librational_0004_capture_flush,"function_capture() flushes pending stdout before redirection");
941 : 1 : TEST(test_librational_0004_1,"report() writes formatted errno messages with source location");
942 : 1 : TEST(test_librational_0004_2,"slog() honors logger modes, decorations and silent visibility");
943 : 1 : TEST(test_librational_0004_3,"serp() writes errno, source file and function name");
944 : : #ifndef EVIL_EMPIRE_OS
945 : 1 : TEST(test_librational_0004_4,"report() stays silent when final message formatting fails");
946 : 1 : TEST(test_librational_0004_5,"report() keeps a truncated fixed-size message buffer terminated");
947 : 1 : TEST(test_librational_0004_6,"report() writes fallback text after primary write failure");
948 : 1 : TEST(test_librational_0004_7,"report() stays silent when primary and fallback writes fail");
949 : : #endif
950 : 1 : TEST(test_librational_0004_8,"rational_reconvert() covers empty and REMEMBER edge cases");
951 : 1 : TEST(test_librational_0004_9,"rational_reconvert() names every logger flag");
952 : : #ifndef EVIL_EMPIRE_OS
953 : 1 : TEST(test_librational_0004_10,"slog() stays silent when line allocation fails");
954 : 1 : TEST(test_librational_0004_11,"slog() keeps payloads visible when time formatting fails");
955 : 1 : TEST(test_librational_0004_12,"slog() stays silent when vsnprintf fails");
956 : : #endif
957 : 1 : TEST(test_librational_0004_13,"slog(ERROR) stays silent when no mode accepts ERROR");
958 : : #ifndef EVIL_EMPIRE_OS
959 : 1 : TEST(test_librational_0004_14,"slog(REMEMBER) skips empty or unformatted payloads");
960 : : #endif
961 : 1 : TEST(test_librational_0004_15,"slog() escapes unsafe terminal bytes while preserving normal UTF-8");
962 : :
963 : 1 : RETURN_STATUS;
964 : : }
|