Branch data Line data Source code
1 : : #include "test_librational_all.h"
2 : :
3 : : #include <errno.h>
4 : : #include <fcntl.h>
5 : : #include <poll.h>
6 : : #include <termios.h>
7 : :
8 : : struct color_environment_backup {
9 : : char *no_color;
10 : : char *term;
11 : : bool no_color_was_set;
12 : : bool term_was_set;
13 : : bool snapshot_is_valid;
14 : : };
15 : :
16 : : static LOGMODES captured_markup_log_level = REGULAR | UNDECOR;
17 : : static const char *captured_markup_format = "@{bold}styled@{reset}\n";
18 : :
19 : : /**
20 : : * @brief Write one styled log payload for output capture
21 : : */
22 : 4 : static void capture_styled_log(void)
23 : : {
24 : 4 : rational_logger(captured_markup_log_level,
25 : : __FILE__,
26 : : __LINE__,
27 : : __func__,
28 : : captured_markup_format);
29 : 4 : }
30 : :
31 : : /**
32 : : * @brief Write a styled log payload after a prefix that sanitization expands
33 : : */
34 : 1 : static void capture_styled_log_with_unsafe_prefix(void)
35 : : {
36 : 1 : rational_logger(VERBOSE,
37 : : "\033unsafe.c",
38 : : 7U,
39 : : "unsafe_prefix",
40 : : "@{bold}styled@{reset}\n");
41 : 1 : }
42 : :
43 : : /**
44 : : * @brief Write markup cases that exercise conversions and literal markers
45 : : */
46 : 2 : static void capture_markup_edge_cases(void)
47 : : {
48 : 2 : slog(REGULAR|UNDECOR,"left @{red}%.*s@{reset} %zu\n",3,"value",(size_t)7U);
49 : 2 : slog(REGULAR|UNDECOR,"%s\n","@{red}from data@{reset}");
50 : 2 : slog(REGULAR|UNDECOR,"@{unknown}unknown\n");
51 : 2 : slog(REGULAR|UNDECOR,"@@{red}literal@{reset}\n");
52 : 2 : slog(REGULAR|UNDECOR,"@@@{red}literal@{reset}\n");
53 : 2 : slog(REGULAR|UNDECOR,"@{bold}no automatic reset\n");
54 : 2 : slog(REGULAR|UNDECOR,"@{red}trailing\n@{reset}");
55 : 2 : slog(REGULAR|UNDECOR,"%2$s@{red}%1$s@{reset}\n","first","second");
56 : 2 : slog(REGULAR|UNDECOR,"@{bold}%s","dynamic\n");
57 : 2 : slog(REGULAR|UNDECOR,
58 : : "@{bold}%s@{reset}\n",
59 : : BOLD "allowed" RESET "\033[2Jblocked");
60 : 2 : slog(REGULAR|UNDECOR,"@{red}@{red}repeated@{reset}@{reset}\n");
61 : 2 : slog(REGULAR|UNDECOR,"@{unknown:@{red}}nested@{reset}\n");
62 : 2 : slog(REGULAR|UNDECOR,"@{RED}case sensitive; @{red\n");
63 : 2 : slog(REGULAR|UNDECOR,"@{red}end without reset");
64 : 2 : }
65 : :
66 : : /**
67 : : * @brief Write every supported marker with explicit resets where needed
68 : : */
69 : 2 : static void capture_all_markup_markers(void)
70 : : {
71 : 2 : slog(REGULAR|UNDECOR,
72 : : "@{shorttab}shorttab\n"
73 : : "@{reset}reset\n"
74 : : "@{black}black@{reset}\n"
75 : : "@{gray}gray@{reset}\n"
76 : : "@{red}red@{reset}\n"
77 : : "@{green}green@{reset}\n"
78 : : "@{yellow}yellow@{reset}\n"
79 : : "@{blue}blue@{reset}\n"
80 : : "@{magenta}magenta@{reset}\n"
81 : : "@{cyan}cyan@{reset}\n"
82 : : "@{white}white@{reset}\n"
83 : : "@{bold}bold@{reset}\n"
84 : : "@{boldblack}boldblack@{reset}\n"
85 : : "@{boldred}boldred@{reset}\n"
86 : : "@{boldgreen}boldgreen@{reset}\n"
87 : : "@{boldyellow}boldyellow@{reset}\n"
88 : : "@{boldblue}boldblue@{reset}\n"
89 : : "@{boldmagenta}boldmagenta@{reset}\n"
90 : : "@{boldcyan}boldcyan@{reset}\n"
91 : : "@{boldwhite}boldwhite@{reset}\n");
92 : 2 : }
93 : :
94 : : /**
95 : : * @brief Write one markup log while stdout points to a selected stream
96 : : *
97 : : * @param stream Stream whose descriptor temporarily replaces stdout
98 : : * @return SUCCESS when the log was written and stdout was restored
99 : : */
100 : 6 : static Return write_markup_log_to_stream(FILE *stream)
101 : : {
102 : : /* Status returned by this function through provide()
103 : : Default value assumes successful completion */
104 : 6 : Return status = SUCCESS;
105 : 6 : int saved_stdout_descriptor = -1;
106 : :
107 [ + - - + ]: 6 : if(stream == NULL || fflush(stdout) != 0)
108 : : {
109 : 0 : status = FAILURE;
110 : : }
111 : :
112 [ + - ]: 6 : if(SUCCESS == status)
113 : : {
114 : 6 : saved_stdout_descriptor = dup(STDOUT_FILENO);
115 : :
116 [ - + ]: 6 : if(saved_stdout_descriptor == -1)
117 : : {
118 : 0 : status = FAILURE;
119 : : }
120 : : }
121 : :
122 [ + - - + ]: 6 : if(SUCCESS == status && dup2(fileno(stream),STDOUT_FILENO) == -1)
123 : : {
124 : 0 : status = FAILURE;
125 : : }
126 : :
127 [ + - ]: 6 : if(SUCCESS == status)
128 : : {
129 : 6 : slog(REGULAR|UNDECOR,"@{bold}styled@{reset}\n");
130 : :
131 [ - + ]: 6 : if(fflush(stdout) != 0)
132 : : {
133 : 0 : status = FAILURE;
134 : : }
135 : : }
136 : :
137 [ + - ]: 6 : if(saved_stdout_descriptor != -1)
138 : : {
139 [ - + ]: 6 : if(dup2(saved_stdout_descriptor,STDOUT_FILENO) == -1)
140 : : {
141 : 0 : status = FAILURE;
142 : : }
143 : :
144 : 6 : (void)close(saved_stdout_descriptor);
145 : : }
146 : :
147 : 6 : provide(status);
148 : : }
149 : :
150 : : /**
151 : : * @brief Capture a styled payload written to a pipe
152 : : *
153 : : * @param output Storage for the captured bytes and a terminating null byte
154 : : * @param output_capacity Available bytes in output
155 : : * @return SUCCESS when the complete payload was captured, otherwise FAILURE
156 : : */
157 : 2 : static Return capture_style_write_from_pipe(
158 : : char *output,
159 : : size_t output_capacity)
160 : : {
161 : : /* Status returned by this function through provide()
162 : : Default value assumes successful completion */
163 : 2 : Return status = SUCCESS;
164 : 2 : int pipe_descriptors[2] = {-1,-1};
165 : 2 : FILE *pipe_stream = NULL;
166 : 2 : size_t output_length = 0U;
167 : :
168 [ + - - + ]: 2 : if(output == NULL || output_capacity == 0U)
169 : : {
170 : 0 : status = FAILURE;
171 : : }
172 : :
173 [ + - - + ]: 2 : if(SUCCESS == status && pipe(pipe_descriptors) != 0)
174 : : {
175 : 0 : status = FAILURE;
176 : : }
177 : :
178 [ + - ]: 2 : if(SUCCESS == status)
179 : : {
180 : 2 : pipe_stream = fdopen(pipe_descriptors[1],"w");
181 : :
182 [ - + ]: 2 : if(pipe_stream == NULL)
183 : : {
184 : 0 : status = FAILURE;
185 : : } else {
186 : 2 : pipe_descriptors[1] = -1;
187 : : }
188 : : }
189 : :
190 [ + - ]: 2 : if(SUCCESS == status)
191 : : {
192 : 2 : status = write_markup_log_to_stream(pipe_stream);
193 : :
194 [ - + ]: 2 : if(fflush(pipe_stream) != 0)
195 : : {
196 : 0 : status = FAILURE;
197 : : }
198 : : }
199 : :
200 [ + - ]: 2 : if(pipe_stream != NULL)
201 : : {
202 [ - + ]: 2 : if(fclose(pipe_stream) != 0)
203 : : {
204 : 0 : status = FAILURE;
205 : : }
206 : :
207 : 2 : pipe_stream = NULL;
208 : : }
209 : :
210 [ + - + - ]: 4 : while(SUCCESS == status && output_length + 1U < output_capacity)
211 : : {
212 : 4 : const ssize_t bytes_read = read(
213 : : pipe_descriptors[0],
214 : 4 : output + output_length,
215 : 4 : output_capacity - output_length - 1U);
216 : :
217 [ + + ]: 4 : if(bytes_read > 0)
218 : : {
219 : 2 : output_length += (size_t)bytes_read;
220 [ + - ]: 2 : } else if(bytes_read == 0){
221 : 2 : break;
222 [ # # ]: 0 : } else if(errno != EINTR){
223 : 0 : status = FAILURE;
224 : : }
225 : : }
226 : :
227 [ + - ]: 2 : if(SUCCESS == status)
228 : : {
229 : 2 : output[output_length] = '\0';
230 : :
231 [ - + ]: 2 : if(output_length + 1U == output_capacity)
232 : : {
233 : 0 : char extra_byte = '\0';
234 : 0 : const ssize_t extra_bytes_read = read(pipe_descriptors[0],&extra_byte,sizeof(extra_byte));
235 : :
236 [ # # ]: 0 : if(extra_bytes_read != 0)
237 : : {
238 : 0 : status = FAILURE;
239 : : }
240 : : }
241 : : }
242 : :
243 [ - + ]: 2 : if(pipe_stream != NULL)
244 : : {
245 : 0 : (void)fclose(pipe_stream);
246 [ - + ]: 2 : } else if(pipe_descriptors[1] != -1){
247 : 0 : (void)close(pipe_descriptors[1]);
248 : : }
249 : :
250 [ + - ]: 2 : if(pipe_descriptors[0] != -1)
251 : : {
252 : 2 : (void)close(pipe_descriptors[0]);
253 : : }
254 : :
255 : 2 : provide(status);
256 : : }
257 : :
258 : : /**
259 : : * @brief Capture a styled payload sent through a pseudoterminal
260 : : *
261 : : * @param output Storage for the captured bytes and a terminating null byte
262 : : * @param output_capacity Available bytes in output
263 : : * @return SUCCESS when one complete line was captured, otherwise FAILURE
264 : : */
265 : 4 : static Return capture_style_write_from_terminal(
266 : : char *output,
267 : : size_t output_capacity)
268 : : {
269 : : /* Status returned by this function through provide()
270 : : Default value assumes successful completion */
271 : 4 : Return status = SUCCESS;
272 : 4 : int terminal_descriptor = -1;
273 : 4 : int terminal_peer_descriptor = -1;
274 : 4 : FILE *terminal_stream = NULL;
275 : 4 : size_t output_length = 0U;
276 : :
277 [ + - - + ]: 4 : if(output == NULL || output_capacity == 0U)
278 : : {
279 : 0 : status = FAILURE;
280 : : }
281 : :
282 [ + - ]: 4 : if(SUCCESS == status)
283 : : {
284 : 4 : terminal_descriptor = posix_openpt(O_RDWR | O_NOCTTY);
285 : :
286 [ - + ]: 4 : if(terminal_descriptor == -1)
287 : : {
288 : 0 : status = FAILURE;
289 : : }
290 : : }
291 : :
292 [ + - + - : 4 : if(SUCCESS == status && (grantpt(terminal_descriptor) != 0 || unlockpt(terminal_descriptor) != 0))
- + ]
293 : : {
294 : 0 : status = FAILURE;
295 : : }
296 : :
297 [ + - ]: 4 : if(SUCCESS == status)
298 : : {
299 : 4 : const char *terminal_peer_path = ptsname(terminal_descriptor);
300 : :
301 [ - + ]: 4 : if(terminal_peer_path == NULL)
302 : : {
303 : 0 : status = FAILURE;
304 : : } else {
305 : 4 : terminal_peer_descriptor = open(terminal_peer_path,O_RDWR | O_NOCTTY);
306 : :
307 [ - + ]: 4 : if(terminal_peer_descriptor == -1)
308 : : {
309 : 0 : status = FAILURE;
310 : : }
311 : : }
312 : : }
313 : :
314 [ + - ]: 4 : if(SUCCESS == status)
315 : : {
316 : : struct termios terminal_attributes;
317 : :
318 [ - + ]: 4 : if(tcgetattr(terminal_peer_descriptor,&terminal_attributes) != 0)
319 : : {
320 : 0 : status = FAILURE;
321 : : } else {
322 : 4 : cfmakeraw(&terminal_attributes);
323 : :
324 [ - + ]: 4 : if(tcsetattr(terminal_peer_descriptor,TCSANOW,&terminal_attributes) != 0)
325 : : {
326 : 0 : status = FAILURE;
327 : : }
328 : : }
329 : : }
330 : :
331 [ + - ]: 4 : if(SUCCESS == status)
332 : : {
333 : 4 : terminal_stream = fdopen(terminal_peer_descriptor,"w");
334 : :
335 [ - + ]: 4 : if(terminal_stream == NULL)
336 : : {
337 : 0 : status = FAILURE;
338 : : } else {
339 : 4 : terminal_peer_descriptor = -1;
340 : : }
341 : : }
342 : :
343 [ + - ]: 4 : if(SUCCESS == status)
344 : : {
345 : 4 : status = write_markup_log_to_stream(terminal_stream);
346 : :
347 [ - + ]: 4 : if(fflush(terminal_stream) != 0)
348 : : {
349 : 0 : status = FAILURE;
350 : : }
351 : : }
352 : :
353 [ + - + - ]: 4 : while(SUCCESS == status && output_length + 1U < output_capacity)
354 : : {
355 : 4 : struct pollfd terminal_poll = {
356 : : .fd = terminal_descriptor,
357 : : .events = POLLIN
358 : : };
359 : 4 : int poll_result = -1;
360 : :
361 : : do
362 : : {
363 : 4 : poll_result = poll(&terminal_poll,1,5000);
364 [ - + - - ]: 4 : } while(poll_result == -1 && errno == EINTR);
365 : :
366 [ + - - + ]: 4 : if(poll_result <= 0 || (terminal_poll.revents & (POLLIN | POLLHUP)) == 0)
367 : : {
368 : 0 : status = FAILURE;
369 : 4 : break;
370 : : }
371 : :
372 : 4 : const ssize_t bytes_read = read(
373 : : terminal_descriptor,
374 : 4 : output + output_length,
375 : 4 : output_capacity - output_length - 1U);
376 : :
377 [ + - ]: 4 : if(bytes_read > 0)
378 : : {
379 : 4 : output_length += (size_t)bytes_read;
380 : :
381 [ + - ]: 4 : if(output[output_length - 1U] == '\n')
382 : : {
383 : 4 : break;
384 : : }
385 [ # # ]: 0 : } else if(bytes_read == 0){
386 : 0 : status = FAILURE;
387 [ # # ]: 0 : } else if(errno != EINTR){
388 : 0 : status = FAILURE;
389 : : }
390 : : }
391 : :
392 [ + - ]: 4 : if(SUCCESS == status)
393 : : {
394 : 4 : output[output_length] = '\0';
395 : :
396 [ + - - + ]: 4 : if(output_length == 0U || output[output_length - 1U] != '\n')
397 : : {
398 : 0 : status = FAILURE;
399 : : }
400 : : }
401 : :
402 [ + - ]: 4 : if(terminal_stream != NULL)
403 : : {
404 : 4 : (void)fclose(terminal_stream);
405 [ # # ]: 0 : } else if(terminal_peer_descriptor != -1){
406 : 0 : (void)close(terminal_peer_descriptor);
407 : : }
408 : :
409 [ + - ]: 4 : if(terminal_descriptor != -1)
410 : : {
411 : 4 : (void)close(terminal_descriptor);
412 : : }
413 : :
414 : 4 : provide(status);
415 : : }
416 : :
417 : : /**
418 : : * @brief Save environment settings that affect automatic terminal styles
419 : : *
420 : : * @param backup Storage for duplicated environment values
421 : : * @return SUCCESS when the environment values were saved, otherwise FAILURE
422 : : */
423 : 3 : static Return color_environment_save(struct color_environment_backup *backup)
424 : : {
425 : : /* Status returned by this function through provide()
426 : : Default value assumes successful completion */
427 : 3 : Return status = SUCCESS;
428 : 3 : const char *no_color = getenv("NO_COLOR");
429 : 3 : const char *term = getenv("TERM");
430 : :
431 : 3 : memset(backup,0,sizeof(*backup));
432 : :
433 [ - + ]: 3 : if(no_color != NULL)
434 : : {
435 : 0 : backup->no_color = strdup(no_color);
436 : :
437 [ # # ]: 0 : if(backup->no_color == NULL)
438 : : {
439 : 0 : status = FAILURE;
440 : : } else {
441 : 0 : backup->no_color_was_set = true;
442 : : }
443 : : }
444 : :
445 [ + - - + ]: 3 : if(SUCCESS == status && term != NULL)
446 : : {
447 : 0 : backup->term = strdup(term);
448 : :
449 [ # # ]: 0 : if(backup->term == NULL)
450 : : {
451 : 0 : status = FAILURE;
452 : : } else {
453 : 0 : backup->term_was_set = true;
454 : : }
455 : : }
456 : :
457 [ + - ]: 3 : if(SUCCESS == status)
458 : : {
459 : 3 : backup->snapshot_is_valid = true;
460 : : }
461 : :
462 : 3 : provide(status);
463 : : }
464 : :
465 : : /**
466 : : * @brief Restore environment settings saved before a terminal style test
467 : : *
468 : : * @param backup Saved environment values to restore and release
469 : : * @return SUCCESS when both settings were restored, otherwise FAILURE
470 : : */
471 : 3 : static Return color_environment_restore(struct color_environment_backup *backup)
472 : : {
473 : : /* Status returned by this function through provide()
474 : : Default value assumes successful completion */
475 : 3 : Return status = SUCCESS;
476 : :
477 [ + - ]: 3 : if(true == backup->snapshot_is_valid)
478 : : {
479 [ - + ]: 3 : if(true == backup->no_color_was_set)
480 : : {
481 [ # # ]: 0 : if(0 != setenv("NO_COLOR",backup->no_color,1))
482 : : {
483 : 0 : status = FAILURE;
484 : : }
485 [ - + ]: 3 : } else if(0 != unsetenv("NO_COLOR")){
486 : 0 : status = FAILURE;
487 : : }
488 : :
489 [ - + ]: 3 : if(true == backup->term_was_set)
490 : : {
491 [ # # ]: 0 : if(0 != setenv("TERM",backup->term,1))
492 : : {
493 : 0 : status = FAILURE;
494 : : }
495 [ - + ]: 3 : } else if(0 != unsetenv("TERM")){
496 : 0 : status = FAILURE;
497 : : }
498 : : }
499 : :
500 : 3 : free(backup->term);
501 : 3 : free(backup->no_color);
502 : 3 : backup->term = NULL;
503 : 3 : backup->no_color = NULL;
504 : :
505 : 3 : provide(status);
506 : : }
507 : :
508 : : /**
509 : : * @brief Check terminal style modes, environment overrides, and stream types
510 : : *
511 : : * @return Return describing success or failure
512 : : */
513 : 1 : static Return test_librational_0006_1(void)
514 : : {
515 : 1 : INITTEST;
516 : :
517 : : struct color_environment_backup environment_backup;
518 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
519 : 1 : FILE *regular_file = NULL;
520 : 1 : FILE *pipe_stream = NULL;
521 : 1 : FILE *terminal_stream = NULL;
522 : 1 : int pipe_descriptors[2] = {-1,-1};
523 : 1 : int terminal_descriptor = -1;
524 : :
525 : 1 : run(color_environment_save(&environment_backup));
526 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
527 : 1 : ASSERT(0 == setenv("TERM","xterm",1));
528 : :
529 : 1 : regular_file = tmpfile();
530 : 1 : ASSERT(regular_file != NULL);
531 : 1 : ASSERT(0 == pipe(pipe_descriptors));
532 : :
533 [ + - ]: 1 : if(pipe_descriptors[1] != -1)
534 : : {
535 : 1 : pipe_stream = fdopen(pipe_descriptors[1],"w");
536 : : }
537 : :
538 : 1 : ASSERT(pipe_stream != NULL);
539 : :
540 [ + - ]: 1 : if(pipe_stream != NULL)
541 : : {
542 : 1 : pipe_descriptors[1] = -1;
543 : : }
544 : :
545 : 1 : terminal_descriptor = posix_openpt(O_RDWR | O_NOCTTY);
546 : 1 : ASSERT(terminal_descriptor != -1);
547 : :
548 [ + - ]: 1 : if(terminal_descriptor != -1)
549 : : {
550 : 1 : terminal_stream = fdopen(terminal_descriptor,"w");
551 : : }
552 : :
553 : 1 : ASSERT(terminal_stream != NULL);
554 : :
555 [ + - ]: 1 : if(terminal_stream != NULL)
556 : : {
557 : 1 : terminal_descriptor = -1;
558 : : }
559 : :
560 : 1 : rational_color_mode = COLOR_MODE_AUTO;
561 : 1 : ASSERT(false == rational_color_is_enabled(NULL));
562 : 1 : ASSERT(false == rational_color_is_enabled(regular_file));
563 : 1 : ASSERT(false == rational_color_is_enabled(pipe_stream));
564 : 1 : ASSERT(true == rational_color_is_enabled(terminal_stream));
565 : 1 : errno = EDOM;
566 : 1 : ASSERT(false == rational_color_is_enabled(pipe_stream));
567 : 1 : ASSERT(EDOM == errno);
568 : :
569 : 1 : ASSERT(0 == setenv("NO_COLOR","",1));
570 : 1 : ASSERT(false == rational_color_is_enabled(terminal_stream));
571 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
572 : 1 : ASSERT(0 == setenv("TERM","dumb",1));
573 : 1 : ASSERT(false == rational_color_is_enabled(terminal_stream));
574 : :
575 : 1 : ASSERT(0 == setenv("NO_COLOR","",1));
576 : 1 : rational_color_mode = COLOR_MODE_ALWAYS;
577 : 1 : ASSERT(false == rational_color_is_enabled(NULL));
578 : 1 : ASSERT(true == rational_color_is_enabled(regular_file));
579 : 1 : ASSERT(true == rational_color_is_enabled(terminal_stream));
580 : :
581 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
582 : 1 : ASSERT(0 == setenv("TERM","xterm",1));
583 : 1 : rational_color_mode = COLOR_MODE_NEVER;
584 : 1 : ASSERT(false == rational_color_is_enabled(terminal_stream));
585 : :
586 : 1 : rational_color_mode = (RATIONAL_COLOR_MODE)0xffU;
587 : 1 : ASSERT(false == rational_color_is_enabled(terminal_stream));
588 : :
589 [ + - ]: 1 : if(terminal_stream != NULL)
590 : : {
591 : 1 : (void)fclose(terminal_stream);
592 [ # # ]: 0 : } else if(terminal_descriptor != -1){
593 : 0 : (void)close(terminal_descriptor);
594 : : }
595 : :
596 [ + - ]: 1 : if(pipe_stream != NULL)
597 : : {
598 : 1 : (void)fclose(pipe_stream);
599 [ # # ]: 0 : } else if(pipe_descriptors[1] != -1){
600 : 0 : (void)close(pipe_descriptors[1]);
601 : : }
602 : :
603 [ + - ]: 1 : if(pipe_descriptors[0] != -1)
604 : : {
605 : 1 : (void)close(pipe_descriptors[0]);
606 : : }
607 : :
608 [ + - ]: 1 : if(regular_file != NULL)
609 : : {
610 : 1 : (void)fclose(regular_file);
611 : : }
612 : :
613 : 1 : rational_color_mode = initial_color_mode;
614 : 1 : call(color_environment_restore(&environment_backup));
615 : :
616 : 1 : RETURN_STATUS;
617 : : }
618 : :
619 : : /**
620 : : * @brief Check that automatic style detection follows descriptor redirection
621 : : *
622 : : * @return Return describing success or failure
623 : : */
624 : 1 : static Return test_librational_0006_2(void)
625 : : {
626 : 1 : INITTEST;
627 : :
628 : : struct color_environment_backup environment_backup;
629 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
630 : 1 : int saved_stdout_descriptor = -1;
631 : 1 : int saved_stderr_descriptor = -1;
632 : 1 : int terminal_descriptor = -1;
633 : 1 : FILE *regular_file = NULL;
634 : :
635 : 1 : run(color_environment_save(&environment_backup));
636 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
637 : 1 : ASSERT(0 == setenv("TERM","xterm",1));
638 : :
639 : 1 : saved_stdout_descriptor = dup(STDOUT_FILENO);
640 : 1 : saved_stderr_descriptor = dup(STDERR_FILENO);
641 : 1 : terminal_descriptor = posix_openpt(O_RDWR | O_NOCTTY);
642 : 1 : regular_file = tmpfile();
643 : 1 : ASSERT(saved_stdout_descriptor != -1);
644 : 1 : ASSERT(saved_stderr_descriptor != -1);
645 : 1 : ASSERT(terminal_descriptor != -1);
646 : 1 : ASSERT(regular_file != NULL);
647 : 1 : ASSERT(0 == fflush(stdout));
648 : 1 : ASSERT(0 == fflush(stderr));
649 : :
650 [ + - + - ]: 1 : if(SUCCESS == status && terminal_descriptor != -1)
651 : : {
652 : 1 : ASSERT(dup2(terminal_descriptor,STDOUT_FILENO) != -1);
653 : : }
654 : :
655 [ + - + - ]: 1 : if(SUCCESS == status && regular_file != NULL)
656 : : {
657 : 1 : ASSERT(dup2(fileno(regular_file),STDERR_FILENO) != -1);
658 : : }
659 : :
660 : 1 : rational_color_mode = COLOR_MODE_AUTO;
661 : 1 : ASSERT(true == rational_color_is_enabled(stdout));
662 : 1 : ASSERT(false == rational_color_is_enabled(stderr));
663 : :
664 [ + - + - ]: 1 : if(SUCCESS == status && regular_file != NULL)
665 : : {
666 : 1 : ASSERT(dup2(fileno(regular_file),STDOUT_FILENO) != -1);
667 : : }
668 : :
669 [ + - + - ]: 1 : if(SUCCESS == status && terminal_descriptor != -1)
670 : : {
671 : 1 : ASSERT(dup2(terminal_descriptor,STDERR_FILENO) != -1);
672 : : }
673 : :
674 : 1 : ASSERT(false == rational_color_is_enabled(stdout));
675 : 1 : ASSERT(true == rational_color_is_enabled(stderr));
676 : :
677 [ - + ]: 1 : if(0 != fflush(stdout))
678 : : {
679 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
680 : 0 : status = FAILURE;
681 : : }
682 : :
683 [ - + ]: 1 : if(0 != fflush(stderr))
684 : : {
685 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
686 : 0 : status = FAILURE;
687 : : }
688 : :
689 [ + - ]: 1 : if(saved_stdout_descriptor != -1)
690 : : {
691 [ - + ]: 1 : if(dup2(saved_stdout_descriptor,STDOUT_FILENO) == -1)
692 : : {
693 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
694 : 0 : status = FAILURE;
695 : : }
696 : :
697 : 1 : (void)close(saved_stdout_descriptor);
698 : : }
699 : :
700 [ + - ]: 1 : if(saved_stderr_descriptor != -1)
701 : : {
702 [ - + ]: 1 : if(dup2(saved_stderr_descriptor,STDERR_FILENO) == -1)
703 : : {
704 : 0 : testitall_failure_location_record(__FILE__,__func__,__LINE__);
705 : 0 : status = FAILURE;
706 : : }
707 : :
708 : 1 : (void)close(saved_stderr_descriptor);
709 : : }
710 : :
711 [ + - ]: 1 : if(terminal_descriptor != -1)
712 : : {
713 : 1 : (void)close(terminal_descriptor);
714 : : }
715 : :
716 [ + - ]: 1 : if(regular_file != NULL)
717 : : {
718 : 1 : (void)fclose(regular_file);
719 : : }
720 : :
721 : 1 : rational_color_mode = initial_color_mode;
722 : 1 : call(color_environment_restore(&environment_backup));
723 : :
724 : 1 : RETURN_STATUS;
725 : : }
726 : :
727 : : /**
728 : : * @brief Check the bytes produced by terminal style modes and overrides
729 : : *
730 : : * @return Return describing success or failure
731 : : */
732 : 1 : static Return test_librational_0006_3(void)
733 : : {
734 : 1 : INITTEST;
735 : :
736 : 1 : struct color_environment_backup environment_backup = {0};
737 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
738 : 1 : const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
739 : : static const char expected_styled_output[] = BOLD "styled" RESET "\n";
740 : : static const char expected_combined_style_output[] = BOLD RED "styled" RESET "\n";
741 : : static const char expected_prefixed_style_output[] = "TESTING:" BOLD "styled" RESET "\n";
742 : : static const char expected_sanitized_prefix_suffix[] = "\\x1Bunsafe.c:007:unsafe_prefix:" BOLD "styled" RESET "\n";
743 : : static const char expected_plain_output[] = "styled\n";
744 : 1 : char pipe_output[64] = {0};
745 : 1 : char terminal_output[64] = {0};
746 : :
747 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
748 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
749 : :
750 : 1 : run(color_environment_save(&environment_backup));
751 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
752 : 1 : ASSERT(0 == setenv("TERM","xterm",1));
753 : 1 : rational_logger_mode = REGULAR;
754 : 1 : captured_markup_log_level = REGULAR | UNDECOR;
755 : 1 : captured_markup_format = "@{bold}styled@{reset}\n";
756 : :
757 : 1 : rational_color_mode = COLOR_MODE_ALWAYS;
758 : 1 : ASSERT(SUCCESS == function_capture(
759 : : capture_styled_log,
760 : : captured_stdout,
761 : : captured_stderr));
762 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_styled_output));
763 : 1 : ASSERT(captured_stderr->length == 0U);
764 : :
765 : 1 : captured_markup_format = "@{bold}@{red}styled@{reset}\n";
766 : 1 : ASSERT(SUCCESS == function_capture(
767 : : capture_styled_log,
768 : : captured_stdout,
769 : : captured_stderr));
770 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_combined_style_output));
771 : 1 : ASSERT(captured_stderr->length == 0U);
772 : :
773 : 1 : rational_logger_mode = TESTING;
774 : 1 : captured_markup_log_level = TESTING;
775 : 1 : captured_markup_format = "@{bold}styled@{reset}\n";
776 : 1 : ASSERT(SUCCESS == function_capture(
777 : : capture_styled_log,
778 : : captured_stdout,
779 : : captured_stderr));
780 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_prefixed_style_output));
781 : 1 : ASSERT(captured_stderr->length == 0U);
782 : :
783 : 1 : rational_logger_mode = VERBOSE;
784 : 1 : ASSERT(SUCCESS == function_capture(
785 : : capture_styled_log_with_unsafe_prefix,
786 : : captured_stdout,
787 : : captured_stderr));
788 : 1 : ASSERT(NULL != strstr(m_text(captured_stdout),expected_sanitized_prefix_suffix));
789 : 1 : ASSERT(captured_stderr->length == 0U);
790 : :
791 : 1 : rational_logger_mode = REGULAR;
792 : 1 : captured_markup_log_level = REGULAR | UNDECOR;
793 : 1 : captured_markup_format = "@{bold}styled@{reset}\n";
794 : :
795 : 1 : rational_color_mode = COLOR_MODE_NEVER;
796 : 1 : ASSERT(SUCCESS == function_capture(
797 : : capture_styled_log,
798 : : captured_stdout,
799 : : captured_stderr));
800 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_plain_output));
801 : 1 : ASSERT(NULL == strchr(m_text(captured_stdout),'\033'));
802 : 1 : ASSERT(NULL == strstr(m_text(captured_stdout),RESET));
803 : 1 : ASSERT(captured_stderr->length == 0U);
804 : :
805 : 1 : rational_color_mode = COLOR_MODE_ALWAYS;
806 : 1 : ASSERT(SUCCESS == capture_style_write_from_pipe(pipe_output,sizeof(pipe_output)));
807 : 1 : ASSERT(0 == strcmp(pipe_output,expected_styled_output));
808 : :
809 : 1 : rational_color_mode = COLOR_MODE_NEVER;
810 : 1 : ASSERT(SUCCESS == capture_style_write_from_terminal(terminal_output,sizeof(terminal_output)));
811 : 1 : ASSERT(0 == strcmp(terminal_output,expected_plain_output));
812 : 1 : ASSERT(NULL == strchr(terminal_output,'\033'));
813 : 1 : ASSERT(NULL == strstr(terminal_output,RESET));
814 : :
815 : 1 : rational_color_mode = COLOR_MODE_AUTO;
816 : 1 : ASSERT(SUCCESS == capture_style_write_from_pipe(pipe_output,sizeof(pipe_output)));
817 : 1 : ASSERT(0 == strcmp(pipe_output,expected_plain_output));
818 : 1 : ASSERT(NULL == strchr(pipe_output,'\033'));
819 : 1 : ASSERT(NULL == strstr(pipe_output,RESET));
820 : :
821 : 1 : ASSERT(SUCCESS == capture_style_write_from_terminal(terminal_output,sizeof(terminal_output)));
822 : 1 : ASSERT(0 == strcmp(terminal_output,expected_styled_output));
823 : :
824 : 1 : ASSERT(0 == setenv("NO_COLOR","",1));
825 : 1 : ASSERT(SUCCESS == capture_style_write_from_terminal(terminal_output,sizeof(terminal_output)));
826 : 1 : ASSERT(0 == strcmp(terminal_output,expected_plain_output));
827 : 1 : ASSERT(NULL == strchr(terminal_output,'\033'));
828 : 1 : ASSERT(NULL == strstr(terminal_output,RESET));
829 : :
830 : 1 : ASSERT(0 == unsetenv("NO_COLOR"));
831 : 1 : ASSERT(0 == setenv("TERM","dumb",1));
832 : 1 : ASSERT(SUCCESS == capture_style_write_from_terminal(terminal_output,sizeof(terminal_output)));
833 : 1 : ASSERT(0 == strcmp(terminal_output,expected_plain_output));
834 : 1 : ASSERT(NULL == strchr(terminal_output,'\033'));
835 : 1 : ASSERT(NULL == strstr(terminal_output,RESET));
836 : :
837 : 1 : captured_markup_log_level = REGULAR | UNDECOR;
838 : 1 : captured_markup_format = "@{bold}styled@{reset}\n";
839 : 1 : rational_logger_mode = initial_logger_mode;
840 : 1 : rational_color_mode = initial_color_mode;
841 : 1 : call(color_environment_restore(&environment_backup));
842 : 1 : call(m_del(captured_stdout));
843 : 1 : call(m_del(captured_stderr));
844 : :
845 : 1 : RETURN_STATUS;
846 : : }
847 : :
848 : : /**
849 : : * @brief Check literal marker replacement after formatting and sanitization
850 : : *
851 : : * @return Return describing success or failure
852 : : */
853 : 1 : static Return test_librational_0006_4(void)
854 : : {
855 : 1 : INITTEST;
856 : :
857 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
858 : 1 : const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
859 : : static const char expected_output[] =
860 : : "left " RED "val" RESET " 7\n"
861 : : RED "from data" RESET "\n"
862 : : "@{unknown}unknown\n"
863 : : "@" RED "literal" RESET "\n"
864 : : "@@" RED "literal" RESET "\n"
865 : : BOLD "no automatic reset\n"
866 : : RED "trailing\n" RESET
867 : : "second" RED "first" RESET "\n"
868 : : BOLD "dynamic\n"
869 : : BOLD "\\x1B[1mallowed\\x1B[0m\\x1B[2Jblocked" RESET "\n"
870 : : RED RED "repeated" RESET RESET "\n"
871 : : "@{unknown:" RED "}nested" RESET "\n"
872 : : "@{RED}case sensitive; @{red\n"
873 : : RED "end without reset";
874 : : static const char expected_plain_output[] =
875 : : "left val 7\n"
876 : : "from data\n"
877 : : "@{unknown}unknown\n"
878 : : "@literal\n"
879 : : "@@literal\n"
880 : : "no automatic reset\n"
881 : : "trailing\n"
882 : : "secondfirst\n"
883 : : "dynamic\n"
884 : : "\\x1B[1mallowed\\x1B[0m\\x1B[2Jblocked\n"
885 : : "repeated\n"
886 : : "@{unknown:}nested\n"
887 : : "@{RED}case sensitive; @{red\n"
888 : : "end without reset";
889 : :
890 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
891 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
892 : :
893 : 1 : rational_logger_mode = REGULAR;
894 : 1 : rational_color_mode = COLOR_MODE_ALWAYS;
895 : 1 : ASSERT(SUCCESS == function_capture(
896 : : capture_markup_edge_cases,
897 : : captured_stdout,
898 : : captured_stderr));
899 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_output));
900 : 1 : ASSERT(captured_stderr->length == 0U);
901 : :
902 : 1 : rational_color_mode = COLOR_MODE_NEVER;
903 : 1 : ASSERT(SUCCESS == function_capture(
904 : : capture_markup_edge_cases,
905 : : captured_stdout,
906 : : captured_stderr));
907 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_plain_output));
908 : 1 : ASSERT(captured_stderr->length == 0U);
909 : :
910 : 1 : rational_logger_mode = initial_logger_mode;
911 : 1 : rational_color_mode = initial_color_mode;
912 : 1 : call(m_del(captured_stdout));
913 : 1 : call(m_del(captured_stderr));
914 : :
915 : 1 : RETURN_STATUS;
916 : : }
917 : :
918 : : /**
919 : : * @brief Check all marker replacements in colored and plain output
920 : : *
921 : : * @return Return describing success or failure
922 : : */
923 : 1 : static Return test_librational_0006_5(void)
924 : : {
925 : 1 : INITTEST;
926 : :
927 : 1 : const RATIONAL_COLOR_MODE initial_color_mode = atomic_load_explicit(&rational_color_mode,memory_order_relaxed);
928 : 1 : const LOGMODES initial_logger_mode = atomic_load_explicit(&rational_logger_mode,memory_order_relaxed);
929 : : static const char expected_output[] =
930 : : SHORTTAB "shorttab\n"
931 : : RESET "reset\n"
932 : : BLACK "black" RESET "\n"
933 : : GRAY "gray" RESET "\n"
934 : : RED "red" RESET "\n"
935 : : GREEN "green" RESET "\n"
936 : : YELLOW "yellow" RESET "\n"
937 : : BLUE "blue" RESET "\n"
938 : : MAGENTA "magenta" RESET "\n"
939 : : CYAN "cyan" RESET "\n"
940 : : WHITE "white" RESET "\n"
941 : : BOLD "bold" RESET "\n"
942 : : BOLDBLACK "boldblack" RESET "\n"
943 : : BOLDRED "boldred" RESET "\n"
944 : : BOLDGREEN "boldgreen" RESET "\n"
945 : : BOLDYELLOW "boldyellow" RESET "\n"
946 : : BOLDBLUE "boldblue" RESET "\n"
947 : : BOLDMAGENTA "boldmagenta" RESET "\n"
948 : : BOLDCYAN "boldcyan" RESET "\n"
949 : : BOLDWHITE "boldwhite" RESET "\n";
950 : : static const char expected_plain_output[] =
951 : : "shorttab\nreset\nblack\ngray\nred\ngreen\nyellow\nblue\nmagenta\ncyan\nwhite\n"
952 : : "bold\nboldblack\nboldred\nboldgreen\nboldyellow\nboldblue\nboldmagenta\nboldcyan\nboldwhite\n";
953 : :
954 : 1 : m_create(char,captured_stdout,MEMORY_STRING);
955 : 1 : m_create(char,captured_stderr,MEMORY_STRING);
956 : :
957 : 1 : rational_logger_mode = REGULAR;
958 : 1 : rational_color_mode = COLOR_MODE_ALWAYS;
959 : 1 : ASSERT(SUCCESS == function_capture(
960 : : capture_all_markup_markers,
961 : : captured_stdout,
962 : : captured_stderr));
963 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_output));
964 : 1 : ASSERT(captured_stderr->length == 0U);
965 : :
966 : 1 : rational_color_mode = COLOR_MODE_NEVER;
967 : 1 : ASSERT(SUCCESS == function_capture(
968 : : capture_all_markup_markers,
969 : : captured_stdout,
970 : : captured_stderr));
971 : 1 : ASSERT(0 == strcmp(m_text(captured_stdout),expected_plain_output));
972 : 1 : ASSERT(captured_stderr->length == 0U);
973 : :
974 : 1 : rational_logger_mode = initial_logger_mode;
975 : 1 : rational_color_mode = initial_color_mode;
976 : 1 : call(m_del(captured_stdout));
977 : 1 : call(m_del(captured_stderr));
978 : :
979 : 1 : RETURN_STATUS;
980 : : }
981 : :
982 : : /**
983 : : * @brief Check literal replacement sizes, allocation, and nonrecursive matches
984 : : *
985 : : * @return Return describing success or failure
986 : : */
987 : 1 : static Return test_librational_0006_6(void)
988 : : {
989 : 1 : INITTEST;
990 : :
991 : 1 : char *text = strdup("one one one");
992 : 1 : char *unchanged_address = text;
993 : 1 : size_t text_length = sizeof("one one one") - 1U;
994 : :
995 : 1 : ASSERT(text != NULL);
996 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"one","x"));
997 : 1 : ASSERT(text == unchanged_address);
998 : 1 : ASSERT(0 == strcmp(text,"x x x"));
999 : 1 : ASSERT(5U == text_length);
1000 : :
1001 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"x","y"));
1002 : 1 : ASSERT(text == unchanged_address);
1003 : 1 : ASSERT(0 == strcmp(text,"y y y"));
1004 : 1 : ASSERT(5U == text_length);
1005 : :
1006 : : #ifndef EVIL_EMPIRE_OS
1007 [ + - ]: 1 : if(SUCCESS == status)
1008 : : {
1009 : 1 : testmocking_realloc_fail_next(1);
1010 : 1 : const bool replacement_succeeded = rational_logger_replace_all(&text,&text_length,"y","red");
1011 : 1 : testmocking_realloc_disable();
1012 : 1 : ASSERT(false == replacement_succeeded);
1013 : 1 : ASSERT(text == unchanged_address);
1014 : 1 : ASSERT(0 == strcmp(text,"y y y"));
1015 : 1 : ASSERT(5U == text_length);
1016 : : }
1017 : : #endif
1018 : :
1019 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"y","red"));
1020 : 1 : ASSERT(0 == strcmp(text,"red red red"));
1021 : 1 : ASSERT(11U == text_length);
1022 : :
1023 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"red","redred"));
1024 : 1 : ASSERT(0 == strcmp(text,"redred redred redred"));
1025 : 1 : ASSERT(20U == text_length);
1026 : :
1027 : 1 : unchanged_address = text;
1028 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"missing","longer replacement"));
1029 : 1 : ASSERT(text == unchanged_address);
1030 : 1 : ASSERT(0 == strcmp(text,"redred redred redred"));
1031 : 1 : ASSERT(20U == text_length);
1032 : 1 : ASSERT(false == rational_logger_replace_all(&text,&text_length,"","replacement"));
1033 : 1 : ASSERT(0 == strcmp(text,"redred redred redred"));
1034 : 1 : ASSERT(20U == text_length);
1035 : :
1036 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"redred",""));
1037 : 1 : ASSERT(text == unchanged_address);
1038 : 1 : ASSERT(0 == strcmp(text," "));
1039 : 1 : ASSERT(2U == text_length);
1040 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length," ",""));
1041 : 1 : ASSERT(0 == strcmp(text,""));
1042 : 1 : ASSERT(0U == text_length);
1043 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"missing","text"));
1044 : 1 : ASSERT(0 == strcmp(text,""));
1045 : 1 : ASSERT(0U == text_length);
1046 : 1 : free(text);
1047 : :
1048 : 1 : text = strdup("aaaaa");
1049 : 1 : text_length = sizeof("aaaaa") - 1U;
1050 : 1 : ASSERT(text != NULL);
1051 : 1 : ASSERT(true == rational_logger_replace_all(&text,&text_length,"aa","bbb"));
1052 : 1 : ASSERT(0 == strcmp(text,"bbbbbba"));
1053 : 1 : ASSERT(7U == text_length);
1054 : 1 : free(text);
1055 : :
1056 : 1 : RETURN_STATUS;
1057 : : }
1058 : :
1059 : : /**
1060 : : * @brief Exercise terminal style policy decisions and logger integration
1061 : : *
1062 : : * @return SUCCESS when all terminal style policy checks pass
1063 : : */
1064 : 1 : Return test_librational_0006(void)
1065 : : {
1066 : 1 : INITTEST;
1067 : :
1068 : 1 : TEST(test_librational_0006_1,"terminal colors honor modes, environment settings, files, pipes, and pseudoterminals");
1069 : 1 : TEST(test_librational_0006_2,"terminal color detection follows independent stdout and stderr redirection");
1070 : 1 : TEST(test_librational_0006_3,"logger markup produces deterministic output for modes, overrides, pipes, and pseudoterminals");
1071 : 1 : TEST(test_librational_0006_4,"formatted markup uses literal replacement without escaping or automatic resets");
1072 : 1 : TEST(test_librational_0006_5,"all supported markers produce exact colored and plain output");
1073 : 1 : TEST(test_librational_0006_6,"literal replacement grows, shrinks, and preserves nonrecursive matching");
1074 : :
1075 : 1 : RETURN_STATUS;
1076 : : }
|