LCOV - code coverage report
Current view: top level - tests/unit - test_watch_sigpipe.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 38 38
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /**
       2              :  * @file test_watch_sigpipe.c
       3              :  * @brief Unit tests for FEAT-17: SIGPIPE / EPIPE handling in the watch loop.
       4              :  *
       5              :  * Verifies that:
       6              :  *  1. signal(SIGPIPE, SIG_IGN) prevents the process from being killed when
       7              :  *     writing to a broken pipe — write(2) returns -1 with errno==EPIPE.
       8              :  *  2. A helper that mimics the watch-loop write path returns a "stop" signal
       9              :  *     (rather than crashing) when the downstream pipe fd is closed.
      10              :  */
      11              : 
      12              : #include "test_helpers.h"
      13              : 
      14              : #include <errno.h>
      15              : #include <signal.h>
      16              : #include <stdio.h>
      17              : #include <string.h>
      18              : #include <unistd.h>
      19              : 
      20              : /* ------------------------------------------------------------------ */
      21              : /* Helpers                                                             */
      22              : /* ------------------------------------------------------------------ */
      23              : 
      24              : /**
      25              :  * Mimics the per-message write path used in cmd_watch():
      26              :  *   printf() → fflush(stdout), redirected to a FILE* backed by @p fd.
      27              :  *
      28              :  * Returns 0 on success, 1 if EPIPE was detected (clean stop requested).
      29              :  */
      30            2 : static int watch_write_line(int fd, const char *line) {
      31            2 :     FILE *f = fdopen(dup(fd), "w");
      32            2 :     if (!f) return 1; /* cannot wrap fd */
      33            2 :     int ret = 0;
      34            2 :     if (fputs(line, f) == EOF || fflush(f) != 0) {
      35            1 :         if (errno == EPIPE) ret = 1;
      36              :     }
      37            2 :     fclose(f);
      38            2 :     return ret;
      39              : }
      40              : 
      41              : /* ------------------------------------------------------------------ */
      42              : /* Tests                                                               */
      43              : /* ------------------------------------------------------------------ */
      44              : 
      45              : /**
      46              :  * After signal(SIGPIPE, SIG_IGN), writing to the write-end of a pipe
      47              :  * whose read-end is already closed must NOT kill the process; instead
      48              :  * write(2) / fflush must return an error with errno == EPIPE.
      49              :  */
      50            1 : static void test_sigpipe_ignored_write_returns_epipe(void) {
      51              :     /* Install SIG_IGN — mirrors what cmd_watch() now does. */
      52            1 :     signal(SIGPIPE, SIG_IGN);
      53              : 
      54              :     int fds[2];
      55            1 :     ASSERT(pipe(fds) == 0, "pipe(): must succeed");
      56              : 
      57              :     /* Close the read end — any write to fds[1] will now yield EPIPE. */
      58            1 :     close(fds[0]);
      59              : 
      60              :     /* Write to the broken pipe write end directly. */
      61            1 :     errno = 0;
      62            1 :     ssize_t n = write(fds[1], "hello\n", 6);
      63            1 :     int saved_errno = errno;
      64              : 
      65            1 :     close(fds[1]);
      66              : 
      67            1 :     ASSERT(n == -1,             "write to broken pipe: must return -1");
      68            1 :     ASSERT(saved_errno == EPIPE, "write to broken pipe: errno must be EPIPE");
      69              : }
      70              : 
      71              : /**
      72              :  * The watch-loop write helper must return 1 (stop) when the downstream
      73              :  * pipe is closed, and must not crash.
      74              :  */
      75            1 : static void test_watch_write_detects_epipe(void) {
      76            1 :     signal(SIGPIPE, SIG_IGN);
      77              : 
      78              :     int fds[2];
      79            1 :     ASSERT(pipe(fds) == 0, "pipe(): must succeed");
      80              : 
      81              :     /* Close the read end to simulate 'head' exiting. */
      82            1 :     close(fds[0]);
      83              : 
      84            1 :     int stop = watch_write_line(fds[1], "test line\n");
      85              : 
      86            1 :     close(fds[1]);
      87              : 
      88            1 :     ASSERT(stop == 1, "watch_write_line: must return 1 (stop) on EPIPE");
      89              : }
      90              : 
      91              : /**
      92              :  * When the pipe is intact the helper must return 0 (keep running).
      93              :  */
      94            1 : static void test_watch_write_succeeds_on_open_pipe(void) {
      95            1 :     signal(SIGPIPE, SIG_IGN);
      96              : 
      97              :     int fds[2];
      98            1 :     ASSERT(pipe(fds) == 0, "pipe(): must succeed");
      99              : 
     100            1 :     int stop = watch_write_line(fds[1], "test line\n");
     101              : 
     102              :     /* Drain so the kernel buffer does not fill up; result intentionally ignored. */
     103              :     char buf[64];
     104            1 :     ssize_t _n = read(fds[0], buf, sizeof(buf));
     105              :     (void)_n;
     106              : 
     107            1 :     close(fds[0]);
     108            1 :     close(fds[1]);
     109              : 
     110            1 :     ASSERT(stop == 0, "watch_write_line: must return 0 on open pipe");
     111              : }
     112              : 
     113              : /* ------------------------------------------------------------------ */
     114              : /* Suite entry point                                                   */
     115              : /* ------------------------------------------------------------------ */
     116              : 
     117            1 : void run_watch_sigpipe_tests(void) {
     118            1 :     RUN_TEST(test_sigpipe_ignored_write_returns_epipe);
     119            1 :     RUN_TEST(test_watch_write_detects_epipe);
     120            1 :     RUN_TEST(test_watch_write_succeeds_on_open_pipe);
     121            1 : }
        

Generated by: LCOV version 2.0-1