Line data Source code
1 : /**
2 : * @file test_input_line_pty.c
3 : * @brief PTY-based functional tests for InputLine and PathComplete.
4 : *
5 : * Usage: test-pty-input-line <path-to-input-line-harness>
6 : *
7 : * Each test launches the harness binary inside a PTY, sends keystrokes,
8 : * and inspects the virtual screen to verify correct behaviour.
9 : */
10 :
11 : #define _DEFAULT_SOURCE
12 : #define _XOPEN_SOURCE 600
13 :
14 : #include "ptytest.h"
15 : #include "pty_assert.h"
16 :
17 : /* Minimal RUN_TEST compatible with g_tests_run / g_tests_failed globals */
18 : #ifndef RUN_TEST
19 : #define RUN_TEST(fn) do { \
20 : printf(" %-50s", #fn); \
21 : int _before = g_tests_failed; \
22 : fn(); \
23 : printf("%s\n", (g_tests_failed == _before) ? "OK" : "FAIL"); \
24 : } while(0)
25 : #endif
26 :
27 : #include <stdio.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 : #include <sys/stat.h>
31 : #include <unistd.h>
32 :
33 : /* ── Globals ─────────────────────────────────────────────────────────── */
34 :
35 : int g_tests_run = 0;
36 : int g_tests_failed = 0;
37 :
38 : static char g_harness_bin[512]; /* path to input-line-harness */
39 : static char g_root[256]; /* per-run temp dir for filesystem tests */
40 :
41 : #define WAIT_MS 3000
42 : #define SETTLE_MS 150
43 : #define ROWS 24
44 : #define COLS 80
45 :
46 : /* ── Temp filesystem helpers ─────────────────────────────────────────── */
47 :
48 21 : static void fs_setup(void)
49 : {
50 21 : snprintf(g_root, sizeof(g_root), "/tmp/il_pty_%d", (int)getpid());
51 21 : mkdir(g_root, 0755);
52 21 : }
53 :
54 1 : static void fs_teardown(void)
55 : {
56 : char cmd[512];
57 1 : snprintf(cmd, sizeof(cmd), "rm -rf '/tmp/il_pty_%d'", (int)getpid());
58 1 : int r = system(cmd);
59 : (void)r;
60 1 : }
61 :
62 163 : static void make_file(const char *dir, const char *name)
63 : {
64 : char path[512];
65 163 : snprintf(path, sizeof(path), "%s/%s", dir, name);
66 163 : FILE *f = fopen(path, "w");
67 163 : if (f) fclose(f);
68 163 : }
69 :
70 9 : static void make_subdir(const char *dir, const char *name)
71 : {
72 : char path[512];
73 9 : snprintf(path, sizeof(path), "%s/%s", dir, name);
74 9 : mkdir(path, 0755);
75 9 : }
76 :
77 : /* ── Session helpers ─────────────────────────────────────────────────── */
78 :
79 : /** Open a PTY session running the harness with an optional initial path. */
80 230 : static PtySession *open_harness(const char *initial)
81 : {
82 230 : PtySession *s = pty_open(COLS, ROWS);
83 230 : if (!s) return NULL;
84 :
85 : const char *argv[3];
86 230 : argv[0] = g_harness_bin;
87 230 : argv[1] = initial; /* may be NULL */
88 230 : argv[2] = NULL;
89 :
90 230 : if (pty_run(s, argv) != 0) {
91 0 : pty_close(s);
92 0 : return NULL;
93 : }
94 210 : return s;
95 : }
96 :
97 : /* ── Tests: basic editing ────────────────────────────────────────────── */
98 :
99 : /** Prompt appears on row 0 immediately after launch. */
100 :
101 : /* ── UTF-8 input (accented characters) ──────────────────────────────── */
102 :
103 : /* Typing non-ASCII must insert the whole code point, not a placeholder byte.
104 : *
105 : * terminal_last_printable() reports multi-byte input as the value 1; inserting
106 : * that produced a literal 0x01 in the buffer, so "hőcserélő" came out as
107 : * "h\x01cser\x01l\x01" — and because a control byte occupies no column, the
108 : * rendered cursor drifted away from the real insertion point, making editing
109 : * unreliable. */
110 4 : static void test_utf8_accented_input(void)
111 : {
112 4 : PtySession *s = open_harness(NULL);
113 3 : ASSERT(s != NULL, "utf8: harness launched");
114 3 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
115 :
116 : /* A real-world filename: Hungarian accents, including two-byte ő and é. */
117 3 : pty_send_str(s, "hőcserélő-számla.pdf");
118 3 : pty_settle(s, SETTLE_MS);
119 3 : ASSERT_SCREEN_CONTAINS(s, "hőcserélő-számla.pdf");
120 :
121 3 : pty_send_key(s, PTY_KEY_ENTER);
122 3 : ASSERT_WAIT_FOR(s, "RESULT:", WAIT_MS);
123 : /* The value handed back must be the text that was typed, byte for byte. */
124 3 : ASSERT_SCREEN_CONTAINS(s, "RESULT: hőcserélő-számla.pdf");
125 3 : pty_close(s);
126 : }
127 :
128 : /* Backspace after an accented character must delete the whole code point,
129 : * leaving valid UTF-8 rather than a dangling continuation byte. */
130 3 : static void test_utf8_backspace_deletes_code_point(void)
131 : {
132 3 : PtySession *s = open_harness(NULL);
133 2 : ASSERT(s != NULL, "utf8 bs: harness launched");
134 2 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
135 :
136 2 : pty_send_str(s, "árvíz");
137 2 : pty_settle(s, SETTLE_MS);
138 2 : pty_send_key(s, PTY_KEY_BACK); /* remove 'z' */
139 2 : pty_send_key(s, PTY_KEY_BACK); /* remove 'í' (two bytes) */
140 2 : pty_settle(s, SETTLE_MS);
141 :
142 2 : pty_send_key(s, PTY_KEY_ENTER);
143 2 : ASSERT_WAIT_FOR(s, "RESULT:", WAIT_MS);
144 2 : ASSERT_SCREEN_CONTAINS(s, "RESULT: árv");
145 2 : pty_close(s);
146 : }
147 :
148 : /* Editing in the middle of accented text: the cursor position the terminal
149 : * shows must match where the bytes actually go. */
150 2 : static void test_utf8_insert_in_middle(void)
151 : {
152 2 : PtySession *s = open_harness(NULL);
153 1 : ASSERT(s != NULL, "utf8 mid: harness launched");
154 1 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
155 :
156 1 : pty_send_str(s, "őz");
157 1 : pty_settle(s, SETTLE_MS);
158 1 : pty_send_key(s, PTY_KEY_LEFT); /* between ő and z */
159 1 : pty_send_str(s, "é");
160 1 : pty_settle(s, SETTLE_MS);
161 :
162 1 : pty_send_key(s, PTY_KEY_ENTER);
163 1 : ASSERT_WAIT_FOR(s, "RESULT:", WAIT_MS);
164 1 : ASSERT_SCREEN_CONTAINS(s, "RESULT: őéz");
165 1 : pty_close(s);
166 : }
167 :
168 21 : static void test_prompt_displayed(void)
169 : {
170 21 : PtySession *s = open_harness(NULL);
171 20 : ASSERT(s != NULL, "harness launched");
172 :
173 20 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
174 20 : ASSERT_ROW_CONTAINS(s, 0, "Path:");
175 :
176 20 : pty_send_key(s, PTY_KEY_ESC);
177 20 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
178 20 : pty_close(s);
179 : }
180 :
181 : /** Typing characters echoes them in the input row. */
182 20 : static void test_typing_appears_on_screen(void)
183 : {
184 20 : PtySession *s = open_harness(NULL);
185 19 : ASSERT(s != NULL, "harness launched");
186 19 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
187 :
188 19 : pty_send_str(s, "hello");
189 19 : pty_settle(s, SETTLE_MS);
190 19 : ASSERT_ROW_CONTAINS(s, 0, "hello");
191 :
192 19 : pty_send_key(s, PTY_KEY_ESC);
193 19 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
194 19 : pty_close(s);
195 : }
196 :
197 : /** Backspace removes the last character. */
198 19 : static void test_backspace_deletes_char(void)
199 : {
200 19 : PtySession *s = open_harness(NULL);
201 18 : ASSERT(s != NULL, "harness launched");
202 18 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
203 :
204 18 : pty_send_str(s, "abc");
205 18 : pty_settle(s, SETTLE_MS);
206 18 : pty_send_key(s, PTY_KEY_BACK);
207 18 : pty_settle(s, SETTLE_MS);
208 :
209 : /* 'c' should be gone; 'ab' remains */
210 18 : ASSERT_ROW_CONTAINS(s, 0, "ab");
211 18 : ASSERT(pty_row_contains(s, 0, "abc") == 0, "'abc' gone after backspace");
212 :
213 18 : pty_send_key(s, PTY_KEY_ESC);
214 18 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
215 18 : pty_close(s);
216 : }
217 :
218 : /** Initial text is shown and confirmed verbatim on Enter. */
219 18 : static void test_initial_text_confirmed(void)
220 : {
221 18 : PtySession *s = open_harness("/some/path.txt");
222 17 : ASSERT(s != NULL, "harness launched");
223 17 : ASSERT_WAIT_FOR(s, "/some/path.txt", WAIT_MS);
224 :
225 17 : pty_send_key(s, PTY_KEY_ENTER);
226 17 : ASSERT_WAIT_FOR(s, "RESULT: /some/path.txt", WAIT_MS);
227 17 : pty_close(s);
228 : }
229 :
230 : /** ESC returns CANCELLED without result. */
231 17 : static void test_esc_cancels(void)
232 : {
233 17 : PtySession *s = open_harness("/cancel/me");
234 16 : ASSERT(s != NULL, "harness launched");
235 16 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
236 :
237 16 : pty_send_key(s, PTY_KEY_ESC);
238 16 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
239 16 : ASSERT(pty_screen_contains(s, "RESULT:") == 0, "no RESULT on ESC");
240 16 : pty_close(s);
241 : }
242 :
243 : /** Typed text is returned as the result on Enter. */
244 16 : static void test_typed_text_in_result(void)
245 : {
246 16 : PtySession *s = open_harness(NULL);
247 15 : ASSERT(s != NULL, "harness launched");
248 15 : ASSERT_WAIT_FOR(s, "Path:", WAIT_MS);
249 :
250 15 : pty_send_str(s, "/tmp/myfile.txt");
251 15 : pty_send_key(s, PTY_KEY_ENTER);
252 15 : ASSERT_WAIT_FOR(s, "RESULT: /tmp/myfile.txt", WAIT_MS);
253 15 : pty_close(s);
254 : }
255 :
256 : /** Left/right arrow keys move the cursor (no crash). */
257 15 : static void test_arrow_keys_move_cursor(void)
258 : {
259 15 : PtySession *s = open_harness("abc");
260 14 : ASSERT(s != NULL, "harness launched");
261 14 : ASSERT_WAIT_FOR(s, "abc", WAIT_MS);
262 :
263 14 : pty_send_key(s, PTY_KEY_LEFT);
264 14 : pty_send_key(s, PTY_KEY_LEFT);
265 14 : pty_settle(s, SETTLE_MS);
266 :
267 : /* Insert 'X' before last char: should produce 'aXbc' */
268 14 : pty_send_str(s, "X");
269 14 : pty_settle(s, SETTLE_MS);
270 14 : ASSERT_ROW_CONTAINS(s, 0, "aXbc");
271 :
272 14 : pty_send_key(s, PTY_KEY_ESC);
273 14 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
274 14 : pty_close(s);
275 : }
276 :
277 : /** Home/End jump to start and end of line. */
278 14 : static void test_home_end_keys(void)
279 : {
280 14 : PtySession *s = open_harness("foo");
281 13 : ASSERT(s != NULL, "harness launched");
282 13 : ASSERT_WAIT_FOR(s, "foo", WAIT_MS);
283 :
284 : /* Home → insert at start */
285 13 : pty_send_key(s, PTY_KEY_HOME);
286 13 : pty_send_str(s, "Z");
287 13 : pty_settle(s, SETTLE_MS);
288 13 : ASSERT_ROW_CONTAINS(s, 0, "Zfoo");
289 :
290 : /* End → append at end */
291 13 : pty_send_key(s, PTY_KEY_END);
292 13 : pty_send_str(s, "Y");
293 13 : pty_settle(s, SETTLE_MS);
294 13 : ASSERT_ROW_CONTAINS(s, 0, "ZfooY");
295 :
296 13 : pty_send_key(s, PTY_KEY_ESC);
297 13 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
298 13 : pty_close(s);
299 : }
300 :
301 : /* ── Tests: Tab completion ───────────────────────────────────────────── */
302 :
303 : /** Tab with a single matching file completes it fully. */
304 13 : static void test_tab_single_match(void)
305 : {
306 13 : make_file(g_root, "report.pdf");
307 :
308 : char prefix[512];
309 13 : snprintf(prefix, sizeof(prefix), "%s/rep", g_root);
310 :
311 13 : PtySession *s = open_harness(prefix);
312 12 : ASSERT(s != NULL, "harness launched");
313 12 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
314 :
315 12 : pty_send_key(s, PTY_KEY_TAB);
316 12 : pty_settle(s, SETTLE_MS);
317 :
318 : /* Completion row (row 0) should contain the full filename */
319 12 : ASSERT_ROW_CONTAINS(s, 0, "report.pdf");
320 :
321 12 : pty_send_key(s, PTY_KEY_ENTER);
322 : char expected[512];
323 12 : snprintf(expected, sizeof(expected), "RESULT: %s/report.pdf", g_root);
324 12 : ASSERT_WAIT_FOR(s, expected, WAIT_MS);
325 12 : pty_close(s);
326 : }
327 :
328 : /** Completion bar appears at row 1 when there are multiple matches. */
329 12 : static void test_tab_shows_completion_bar(void)
330 : {
331 12 : make_file(g_root, "aaa.txt");
332 12 : make_file(g_root, "aab.txt");
333 12 : make_file(g_root, "aac.txt");
334 :
335 : char prefix[512];
336 12 : snprintf(prefix, sizeof(prefix), "%s/aa", g_root);
337 :
338 12 : PtySession *s = open_harness(prefix);
339 11 : ASSERT(s != NULL, "harness launched");
340 11 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
341 :
342 11 : pty_send_key(s, PTY_KEY_TAB);
343 11 : pty_settle(s, SETTLE_MS);
344 :
345 : /* Completion bar on row 1 must list all three names */
346 11 : ASSERT_ROW_CONTAINS(s, 1, "aaa.txt");
347 11 : ASSERT_ROW_CONTAINS(s, 1, "aab.txt");
348 11 : ASSERT_ROW_CONTAINS(s, 1, "aac.txt");
349 :
350 11 : pty_send_key(s, PTY_KEY_ESC);
351 11 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
352 11 : pty_close(s);
353 : }
354 :
355 : /** Repeated Tab presses cycle forward through alphabetically sorted matches. */
356 11 : static void test_tab_cycles_forward(void)
357 : {
358 11 : make_file(g_root, "zz_alpha.txt");
359 11 : make_file(g_root, "zz_beta.txt");
360 11 : make_file(g_root, "zz_gamma.txt");
361 :
362 : char prefix[512];
363 11 : snprintf(prefix, sizeof(prefix), "%s/zz_", g_root);
364 :
365 11 : PtySession *s = open_harness(prefix);
366 10 : ASSERT(s != NULL, "harness launched");
367 10 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
368 :
369 : /* 1st Tab → zz_alpha */
370 10 : pty_send_key(s, PTY_KEY_TAB);
371 10 : pty_settle(s, SETTLE_MS);
372 10 : ASSERT_ROW_CONTAINS(s, 0, "zz_alpha.txt");
373 :
374 : /* 2nd Tab → zz_beta */
375 10 : pty_send_key(s, PTY_KEY_TAB);
376 10 : pty_settle(s, SETTLE_MS);
377 10 : ASSERT_ROW_CONTAINS(s, 0, "zz_beta.txt");
378 :
379 : /* 3rd Tab → zz_gamma */
380 10 : pty_send_key(s, PTY_KEY_TAB);
381 10 : pty_settle(s, SETTLE_MS);
382 10 : ASSERT_ROW_CONTAINS(s, 0, "zz_gamma.txt");
383 :
384 : /* 4th Tab wraps back to alpha */
385 10 : pty_send_key(s, PTY_KEY_TAB);
386 10 : pty_settle(s, SETTLE_MS);
387 10 : ASSERT_ROW_CONTAINS(s, 0, "zz_alpha.txt");
388 :
389 10 : pty_send_key(s, PTY_KEY_ESC);
390 10 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
391 10 : pty_close(s);
392 : }
393 :
394 : /** Shift+Tab cycles backward through completions. */
395 10 : static void test_shift_tab_cycles_backward(void)
396 : {
397 10 : make_file(g_root, "xx_one.txt");
398 10 : make_file(g_root, "xx_three.txt");
399 10 : make_file(g_root, "xx_two.txt");
400 :
401 : char prefix[512];
402 10 : snprintf(prefix, sizeof(prefix), "%s/xx_", g_root);
403 :
404 10 : PtySession *s = open_harness(prefix);
405 9 : ASSERT(s != NULL, "harness launched");
406 9 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
407 :
408 : /* First Tab → xx_one (idx 0) */
409 9 : pty_send_key(s, PTY_KEY_TAB);
410 9 : pty_settle(s, SETTLE_MS);
411 9 : ASSERT_ROW_CONTAINS(s, 0, "xx_one.txt");
412 :
413 : /* Shift+Tab backward-wraps to xx_two (last, idx 2) */
414 9 : pty_send(s, "\033[Z", 3);
415 9 : pty_settle(s, SETTLE_MS);
416 9 : ASSERT_ROW_CONTAINS(s, 0, "xx_two.txt");
417 :
418 : /* Another Shift+Tab → xx_three */
419 9 : pty_send(s, "\033[Z", 3);
420 9 : pty_settle(s, SETTLE_MS);
421 9 : ASSERT_ROW_CONTAINS(s, 0, "xx_three.txt");
422 :
423 : /* Another Shift+Tab → xx_one */
424 9 : pty_send(s, "\033[Z", 3);
425 9 : pty_settle(s, SETTLE_MS);
426 9 : ASSERT_ROW_CONTAINS(s, 0, "xx_one.txt");
427 :
428 9 : pty_send_key(s, PTY_KEY_ESC);
429 9 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
430 9 : pty_close(s);
431 : }
432 :
433 : /** Typing '/' after a directory completion triggers a fresh scan inside it. */
434 9 : static void test_slash_enters_subdirectory(void)
435 : {
436 9 : make_subdir(g_root, "docs");
437 9 : make_file(g_root, "docs/guide.pdf");
438 :
439 : char prefix[512];
440 9 : snprintf(prefix, sizeof(prefix), "%s/doc", g_root);
441 :
442 9 : PtySession *s = open_harness(prefix);
443 8 : ASSERT(s != NULL, "harness launched");
444 8 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
445 :
446 : /* Tab completes to "docs" */
447 8 : pty_send_key(s, PTY_KEY_TAB);
448 8 : pty_settle(s, SETTLE_MS);
449 8 : ASSERT_ROW_CONTAINS(s, 0, "docs");
450 :
451 : /* Append '/' */
452 8 : pty_send_str(s, "/");
453 8 : pty_settle(s, SETTLE_MS);
454 :
455 : /* Next Tab scans inside docs/ → guide.pdf */
456 8 : pty_send_key(s, PTY_KEY_TAB);
457 8 : pty_settle(s, SETTLE_MS);
458 8 : ASSERT_ROW_CONTAINS(s, 0, "guide.pdf");
459 :
460 : char expected[512];
461 8 : snprintf(expected, sizeof(expected), "%s/docs/guide.pdf", g_root);
462 8 : pty_send_key(s, PTY_KEY_ENTER);
463 : char want[600];
464 8 : snprintf(want, sizeof(want), "RESULT: %s", expected);
465 8 : ASSERT_WAIT_FOR(s, want, WAIT_MS);
466 8 : pty_close(s);
467 : }
468 :
469 : /** Suffix after cursor is preserved throughout Tab cycling. */
470 8 : static void test_suffix_preserved(void)
471 : {
472 8 : make_file(g_root, "report.pdf");
473 8 : make_file(g_root, "readme.txt");
474 :
475 : /* Initial path: "<root>/filename.pdf", cursor just after '<root>/' */
476 : char initial[512];
477 8 : snprintf(initial, sizeof(initial), "%s/filename.pdf", g_root);
478 :
479 8 : PtySession *s = open_harness(initial);
480 7 : ASSERT(s != NULL, "harness launched");
481 7 : ASSERT_WAIT_FOR(s, initial, WAIT_MS);
482 :
483 : /* Move cursor to just after the '/' (Home, then right to skip "<root>/") */
484 7 : pty_send_key(s, PTY_KEY_HOME);
485 : /* advance past every character in g_root + '/' */
486 7 : size_t steps = strlen(g_root) + 1;
487 126 : for (size_t i = 0; i < steps; i++)
488 119 : pty_send_key(s, PTY_KEY_RIGHT);
489 7 : pty_settle(s, SETTLE_MS);
490 :
491 : /* Tab with empty prefix → first alphabetical match; suffix stays */
492 7 : pty_send_key(s, PTY_KEY_TAB);
493 7 : pty_settle(s, SETTLE_MS);
494 :
495 : /* Suffix "filename.pdf" must still be visible on the input row */
496 7 : ASSERT_ROW_CONTAINS(s, 0, "filename.pdf");
497 :
498 7 : pty_send_key(s, PTY_KEY_ESC);
499 7 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
500 7 : pty_close(s);
501 : }
502 :
503 : /** Hidden files (dot-files) are excluded when prefix is empty. */
504 7 : static void test_hidden_files_excluded(void)
505 : {
506 : char td[512];
507 7 : snprintf(td, sizeof(td), "%s/hidden_test", g_root);
508 7 : mkdir(td, 0755);
509 7 : make_file(td, ".hidden_secret");
510 7 : make_file(td, "visible.cfg");
511 :
512 : char prefix[768];
513 7 : snprintf(prefix, sizeof(prefix), "%s/", td);
514 :
515 7 : PtySession *s = open_harness(prefix);
516 6 : ASSERT(s != NULL, "harness launched");
517 6 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
518 :
519 6 : pty_send_key(s, PTY_KEY_TAB);
520 6 : pty_settle(s, SETTLE_MS);
521 :
522 : /* Input row must show the visible file (first Tab selection) */
523 6 : ASSERT_ROW_CONTAINS(s, 0, "visible.cfg");
524 : /* Hidden file must not appear anywhere on screen */
525 6 : ASSERT(pty_screen_contains(s, ".hidden_secret") == 0,
526 : "hidden file not on screen");
527 :
528 6 : pty_send_key(s, PTY_KEY_ESC);
529 6 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
530 6 : pty_close(s);
531 : }
532 :
533 : /** Dot prefix reveals hidden files. */
534 6 : static void test_dot_prefix_shows_hidden(void)
535 : {
536 6 : make_file(g_root, ".bashrc");
537 6 : make_file(g_root, ".profile");
538 :
539 : char prefix[512];
540 6 : snprintf(prefix, sizeof(prefix), "%s/.", g_root);
541 :
542 6 : PtySession *s = open_harness(prefix);
543 5 : ASSERT(s != NULL, "harness launched");
544 5 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
545 :
546 5 : pty_send_key(s, PTY_KEY_TAB);
547 5 : pty_settle(s, SETTLE_MS);
548 :
549 : /* First alphabetical dot-file (.bashrc) should appear */
550 5 : ASSERT_ROW_CONTAINS(s, 0, ".bashrc");
551 :
552 5 : pty_send_key(s, PTY_KEY_ESC);
553 5 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
554 5 : pty_close(s);
555 : }
556 :
557 : /** No match: buffer is unchanged after Tab. */
558 5 : static void test_no_match_leaves_buf_unchanged(void)
559 : {
560 : /* Use empty dir (g_root has only previously created files from other
561 : * tests — use a fresh subdir to guarantee it is empty). */
562 : char emptydir[512];
563 5 : snprintf(emptydir, sizeof(emptydir), "%s/empty_notest", g_root);
564 5 : mkdir(emptydir, 0755);
565 :
566 : char prefix[768];
567 5 : snprintf(prefix, sizeof(prefix), "%s/zzz", emptydir);
568 :
569 5 : PtySession *s = open_harness(prefix);
570 4 : ASSERT(s != NULL, "harness launched");
571 4 : ASSERT_WAIT_FOR(s, prefix, WAIT_MS);
572 :
573 4 : pty_send_key(s, PTY_KEY_TAB);
574 4 : pty_settle(s, SETTLE_MS);
575 :
576 : /* Input row must still show the unchanged prefix */
577 4 : ASSERT_ROW_CONTAINS(s, 0, prefix);
578 : /* Completion bar (row 1) must not appear / must be empty */
579 4 : ASSERT(pty_row_contains(s, 1, "zzz") == 0, "no completion bar on no match");
580 :
581 4 : pty_send_key(s, PTY_KEY_ESC);
582 4 : ASSERT_WAIT_FOR(s, "CANCELLED", WAIT_MS);
583 4 : pty_close(s);
584 : }
585 :
586 : /* ── Entry point ─────────────────────────────────────────────────────── */
587 :
588 21 : int main(int argc, char *argv[])
589 : {
590 21 : if (argc < 2) {
591 0 : fprintf(stderr, "Usage: %s <input-line-harness>\n", argv[0]);
592 0 : return 1;
593 : }
594 21 : snprintf(g_harness_bin, sizeof(g_harness_bin), "%s", argv[1]);
595 :
596 21 : printf("--- InputLine / PathComplete PTY Test Suite ---\n\n");
597 :
598 21 : fs_setup();
599 :
600 : /* Basic editing */
601 21 : printf("[Basic editing]\n");
602 21 : RUN_TEST(test_prompt_displayed);
603 20 : RUN_TEST(test_typing_appears_on_screen);
604 19 : RUN_TEST(test_backspace_deletes_char);
605 18 : RUN_TEST(test_initial_text_confirmed);
606 17 : RUN_TEST(test_esc_cancels);
607 16 : RUN_TEST(test_typed_text_in_result);
608 15 : RUN_TEST(test_arrow_keys_move_cursor);
609 14 : RUN_TEST(test_home_end_keys);
610 :
611 : /* Tab completion */
612 13 : printf("\n[Tab completion]\n");
613 13 : RUN_TEST(test_tab_single_match);
614 12 : RUN_TEST(test_tab_shows_completion_bar);
615 11 : RUN_TEST(test_tab_cycles_forward);
616 10 : RUN_TEST(test_shift_tab_cycles_backward);
617 9 : RUN_TEST(test_slash_enters_subdirectory);
618 8 : RUN_TEST(test_suffix_preserved);
619 7 : RUN_TEST(test_hidden_files_excluded);
620 6 : RUN_TEST(test_dot_prefix_shows_hidden);
621 5 : RUN_TEST(test_no_match_leaves_buf_unchanged);
622 4 : RUN_TEST(test_utf8_accented_input);
623 3 : RUN_TEST(test_utf8_backspace_deletes_code_point);
624 2 : RUN_TEST(test_utf8_insert_in_middle);
625 :
626 1 : fs_teardown();
627 :
628 1 : printf("\n--- Test Results ---\n");
629 1 : printf("Tests Run: %d\n", g_tests_run);
630 1 : printf("Tests Passed: %d\n", g_tests_run - g_tests_failed);
631 1 : printf("Tests Failed: %d\n", g_tests_failed);
632 :
633 1 : return (g_tests_failed > 0) ? 1 : 0;
634 : }
|