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

            Line data    Source code
       1              : /**
       2              :  * @file test_readline.c
       3              :  * @brief Unit tests for readline.c (history API and non-TTY path).
       4              :  *
       5              :  * The interactive (TTY) path cannot be unit-tested without a real terminal,
       6              :  * so these tests focus on the history API and the non-TTY fallback.
       7              :  */
       8              : 
       9              : #include "test_helpers.h"
      10              : #include "readline.h"
      11              : 
      12              : #include <string.h>
      13              : #include <stdio.h>
      14              : #include <stdlib.h>
      15              : #include <unistd.h>
      16              : 
      17              : /* ---- Test: rl_history_init zeroes the struct ---- */
      18            1 : static void test_history_init(void) {
      19              :     LineHistory h;
      20            1 :     memset(&h, 0xFF, sizeof(h)); /* fill with garbage */
      21            1 :     rl_history_init(&h);
      22            1 :     ASSERT(h.count == 0, "history_init: count must be 0");
      23            1 :     ASSERT(h.head  == 0, "history_init: head must be 0");
      24              : }
      25              : 
      26              : /* ---- Test: rl_history_init with NULL is safe ---- */
      27            1 : static void test_history_init_null(void) {
      28            1 :     rl_history_init(NULL); /* must not crash */
      29            1 :     ASSERT(1, "history_init(NULL): must not crash");
      30              : }
      31              : 
      32              : /* ---- Test: rl_history_add basic ---- */
      33            1 : static void test_history_add_basic(void) {
      34              :     LineHistory h;
      35            1 :     rl_history_init(&h);
      36            1 :     rl_history_add(&h, "hello");
      37            1 :     rl_history_add(&h, "world");
      38            1 :     ASSERT(h.count == 2, "history_add: count must be 2 after two adds");
      39              : }
      40              : 
      41              : /* ---- Test: rl_history_add ignores empty strings ---- */
      42            1 : static void test_history_add_empty(void) {
      43              :     LineHistory h;
      44            1 :     rl_history_init(&h);
      45            1 :     rl_history_add(&h, "");
      46            1 :     ASSERT(h.count == 0, "history_add: empty string must not be added");
      47              : }
      48              : 
      49              : /* ---- Test: rl_history_add ignores NULL ---- */
      50            1 : static void test_history_add_null(void) {
      51              :     LineHistory h;
      52            1 :     rl_history_init(&h);
      53            1 :     rl_history_add(&h, NULL);
      54            1 :     ASSERT(h.count == 0, "history_add: NULL must not be added");
      55              : }
      56              : 
      57              : /* ---- Test: rl_history_add ignores consecutive duplicate ---- */
      58            1 : static void test_history_add_duplicate(void) {
      59              :     LineHistory h;
      60            1 :     rl_history_init(&h);
      61            1 :     rl_history_add(&h, "alpha");
      62            1 :     rl_history_add(&h, "alpha"); /* duplicate */
      63            1 :     ASSERT(h.count == 1, "history_add: consecutive duplicate must not be added");
      64            1 :     rl_history_add(&h, "beta");
      65            1 :     rl_history_add(&h, "alpha"); /* non-consecutive duplicate — should be added */
      66            1 :     ASSERT(h.count == 3, "history_add: non-consecutive duplicate must be added");
      67              : }
      68              : 
      69              : /* ---- Test: rl_history_add wraps around when full ---- */
      70            1 : static void test_history_add_wrap(void) {
      71              :     LineHistory h;
      72            1 :     rl_history_init(&h);
      73              : 
      74              :     char buf[32];
      75          262 :     for (int i = 0; i < RL_HISTORY_MAX + 5; i++) {
      76          261 :         snprintf(buf, sizeof(buf), "line%d", i);
      77          261 :         rl_history_add(&h, buf);
      78              :     }
      79            1 :     ASSERT(h.count == RL_HISTORY_MAX,
      80              :            "history_add wrap: count must not exceed RL_HISTORY_MAX");
      81              : }
      82              : 
      83              : /* ---- Test: rl_readline NULL args return -1 ---- */
      84            1 : static void test_readline_null_args(void) {
      85              :     char buf[64];
      86            1 :     ASSERT(rl_readline(">> ", NULL, 64,   NULL) == -1,
      87              :            "readline: NULL buf must return -1");
      88            1 :     ASSERT(rl_readline(">> ", buf,  0,    NULL) == -1,
      89              :            "readline: zero size must return -1");
      90              : }
      91              : 
      92              : /* ---- Test: rl_readline non-TTY reads from stdin pipe ----
      93              :  *
      94              :  * We redirect a pipe into stdin, call rl_readline, and verify the output.
      95              :  * terminal_is_tty(STDIN_FILENO) returns 0 for a pipe, triggering the
      96              :  * non-TTY fallback path which does a plain fgets().
      97              :  */
      98            1 : static void test_readline_nontty_basic(void) {
      99              :     /* Save original stdin */
     100            1 :     int saved_stdin = dup(STDIN_FILENO);
     101            1 :     if (saved_stdin == -1) {
     102            0 :         ASSERT(0, "readline nontty: dup(stdin) failed — test skipped");
     103              :         return;
     104              :     }
     105              : 
     106              :     int pipefd[2];
     107            1 :     if (pipe(pipefd) != 0) {
     108            0 :         close(saved_stdin);
     109            0 :         ASSERT(0, "readline nontty: pipe() failed — test skipped");
     110              :         return;
     111              :     }
     112              : 
     113              :     /* Write test input into the write end, then close it */
     114            1 :     const char *input = "hello pipe\n";
     115            1 :     { ssize_t w = write(pipefd[1], input, strlen(input)); (void)w; }
     116            1 :     close(pipefd[1]);
     117              : 
     118              :     /* Replace stdin with the read end of the pipe */
     119            1 :     dup2(pipefd[0], STDIN_FILENO);
     120            1 :     close(pipefd[0]);
     121              : 
     122              :     char buf[128];
     123            1 :     int rc = rl_readline(NULL, buf, sizeof(buf), NULL);
     124              : 
     125              :     /* Restore stdin */
     126            1 :     dup2(saved_stdin, STDIN_FILENO);
     127            1 :     close(saved_stdin);
     128              : 
     129            1 :     ASSERT(rc >= 0, "readline nontty: must succeed on pipe input");
     130            1 :     ASSERT(strcmp(buf, "hello pipe") == 0,
     131              :            "readline nontty: must return input without trailing newline");
     132              : }
     133              : 
     134              : /* ---- Test: rl_readline non-TTY strips trailing CR+LF ---- */
     135            1 : static void test_readline_nontty_crlf(void) {
     136            1 :     int saved_stdin = dup(STDIN_FILENO);
     137            1 :     if (saved_stdin == -1) return;
     138              : 
     139              :     int pipefd[2];
     140            1 :     if (pipe(pipefd) != 0) { close(saved_stdin); return; }
     141              : 
     142            1 :     const char *input = "test\r\n";
     143            1 :     { ssize_t w = write(pipefd[1], input, strlen(input)); (void)w; }
     144            1 :     close(pipefd[1]);
     145              : 
     146            1 :     dup2(pipefd[0], STDIN_FILENO);
     147            1 :     close(pipefd[0]);
     148              : 
     149              :     char buf[128];
     150            1 :     int rc = rl_readline(NULL, buf, sizeof(buf), NULL);
     151              : 
     152            1 :     dup2(saved_stdin, STDIN_FILENO);
     153            1 :     close(saved_stdin);
     154              : 
     155            1 :     ASSERT(rc >= 0, "readline nontty crlf: must succeed");
     156            1 :     ASSERT(strcmp(buf, "test") == 0,
     157              :            "readline nontty crlf: must strip CR+LF");
     158              : }
     159              : 
     160              : /* ---- Test: rl_readline non-TTY EOF returns -1 ---- */
     161            1 : static void test_readline_nontty_eof(void) {
     162            1 :     int saved_stdin = dup(STDIN_FILENO);
     163            1 :     if (saved_stdin == -1) return;
     164              : 
     165              :     int pipefd[2];
     166            1 :     if (pipe(pipefd) != 0) { close(saved_stdin); return; }
     167              : 
     168              :     /* Write nothing — immediate EOF */
     169            1 :     close(pipefd[1]);
     170              : 
     171            1 :     dup2(pipefd[0], STDIN_FILENO);
     172            1 :     close(pipefd[0]);
     173              : 
     174              :     char buf[128];
     175            1 :     int rc = rl_readline(NULL, buf, sizeof(buf), NULL);
     176              : 
     177            1 :     dup2(saved_stdin, STDIN_FILENO);
     178            1 :     close(saved_stdin);
     179              : 
     180            1 :     ASSERT(rc == -1, "readline nontty eof: must return -1 on EOF");
     181              : }
     182              : 
     183              : /* ---- Test: rl_readline non-TTY with history parameter is safe ---- */
     184            1 : static void test_readline_nontty_with_history(void) {
     185            1 :     int saved_stdin = dup(STDIN_FILENO);
     186            1 :     if (saved_stdin == -1) return;
     187              : 
     188              :     int pipefd[2];
     189            1 :     if (pipe(pipefd) != 0) { close(saved_stdin); return; }
     190              : 
     191            1 :     const char *input = "cmd arg\n";
     192            1 :     { ssize_t w = write(pipefd[1], input, strlen(input)); (void)w; }
     193            1 :     close(pipefd[1]);
     194              : 
     195            1 :     dup2(pipefd[0], STDIN_FILENO);
     196            1 :     close(pipefd[0]);
     197              : 
     198              :     LineHistory h;
     199            1 :     rl_history_init(&h);
     200              : 
     201              :     char buf[128];
     202            1 :     int rc = rl_readline(">>> ", buf, sizeof(buf), &h);
     203              : 
     204            1 :     dup2(saved_stdin, STDIN_FILENO);
     205            1 :     close(saved_stdin);
     206              : 
     207            1 :     ASSERT(rc >= 0, "readline nontty+hist: must succeed");
     208            1 :     ASSERT(strcmp(buf, "cmd arg") == 0,
     209              :            "readline nontty+hist: content must match");
     210              : }
     211              : 
     212            1 : void run_readline_tests(void) {
     213            1 :     RUN_TEST(test_history_init);
     214            1 :     RUN_TEST(test_history_init_null);
     215            1 :     RUN_TEST(test_history_add_basic);
     216            1 :     RUN_TEST(test_history_add_empty);
     217            1 :     RUN_TEST(test_history_add_null);
     218            1 :     RUN_TEST(test_history_add_duplicate);
     219            1 :     RUN_TEST(test_history_add_wrap);
     220            1 :     RUN_TEST(test_readline_null_args);
     221            1 :     RUN_TEST(test_readline_nontty_basic);
     222            1 :     RUN_TEST(test_readline_nontty_crlf);
     223            1 :     RUN_TEST(test_readline_nontty_eof);
     224            1 :     RUN_TEST(test_readline_nontty_with_history);
     225            1 : }
        

Generated by: LCOV version 2.0-1