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 : }
|