LCOV - code coverage report
Current view: top level - tests/functional/pty - test_ctrl_c_exit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 89.5 % 76 68
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file test_ctrl_c_exit.c
       6              :  * @brief PTY-05 — Ctrl-C exits tg-tui cleanly without leaving raw-mode artefacts.
       7              :  *
       8              :  * Two tests:
       9              :  *
      10              :  *   test_repl_ctrl_c:
      11              :  *     - Launches tg-tui (REPL / no --tui flag).
      12              :  *     - Waits for the "tg> " prompt.
      13              :  *     - Sends Ctrl-C (byte 0x03).
      14              :  *     - Asserts the child exits within 2 seconds with code 0 (graceful: the
      15              :  *       readline layer returns -1 on Ctrl-C and repl() returns 0).
      16              :  *     - Verifies that PTY dimensions are intact (terminal not trashed).
      17              :  *
      18              :  *   test_tui_mode_ctrl_c:
      19              :  *     - Launches tg-tui --tui.
      20              :  *     - Waits for "[dialogs]" in the status row.
      21              :  *     - Sends Ctrl-C.
      22              :  *     - Asserts child exits within 2 seconds with status 130 (128+SIGINT) or 0.
      23              :  *       (terminal_install_cleanup_handlers resets the handler to SIG_DFL and
      24              :  *       re-raises SIGINT, so the shell sees 128+2=130.)
      25              :  *     - Verifies PTY dimensions are intact after exit.
      26              :  *
      27              :  * Depends on:
      28              :  *   - libptytest (PTY-01)
      29              :  *   - pty_tel_stub (PTY-02 infrastructure)
      30              :  *   - tg-tui binary with terminal_install_cleanup_handlers (FEAT-16)
      31              :  */
      32              : 
      33              : #include "ptytest.h"
      34              : #include "pty_assert.h"
      35              : #include "pty_tel_stub.h"
      36              : 
      37              : #include <stdio.h>
      38              : #include <stdlib.h>
      39              : #include <string.h>
      40              : #include <unistd.h>
      41              : #include <sys/stat.h>
      42              : 
      43              : /* ── Test infrastructure ─────────────────────────────────────────────── */
      44              : 
      45              : static int g_tests_run    = 0;
      46              : static int g_tests_failed = 0;
      47              : 
      48              : #define RUN_TEST(fn) do { \
      49              :     printf("  running %s ...\n", #fn); \
      50              :     fn(); \
      51              : } while(0)
      52              : 
      53              : #ifndef TG_TUI_BINARY
      54              : #define TG_TUI_BINARY "bin/tg-tui"
      55              : #endif
      56              : 
      57              : /**
      58              :  * @brief Like ASSERT but jumps to a label on failure (avoids early-return leaks).
      59              :  */
      60              : #define CHECK(cond, msg, label) do { \
      61              :     g_tests_run++; \
      62              :     if (!(cond)) { \
      63              :         printf("  [FAIL] %s:%d: %s\n", __FILE__, __LINE__, (msg)); \
      64              :         g_tests_failed++; \
      65              :         goto label; \
      66              :     } \
      67              : } while(0)
      68              : 
      69              : /**
      70              :  * @brief Like ASSERT_WAIT_FOR but jumps to a label on failure.
      71              :  */
      72              : #define CHECK_WAIT_FOR(s, text, timeout_ms, label) do { \
      73              :     g_tests_run++; \
      74              :     if (pty_wait_for((s), (text), (timeout_ms)) != 0) { \
      75              :         printf("  [FAIL] %s:%d: wait_for(\"%s\", %d ms) timed out\n", \
      76              :                __FILE__, __LINE__, (text), (timeout_ms)); \
      77              :         g_tests_failed++; \
      78              :         goto label; \
      79              :     } \
      80              : } while(0)
      81              : 
      82              : /**
      83              :  * @brief Common setup: unique tmp HOME, session seeded, stub started, env set.
      84              :  * @return 1 on success, 0 on failure.
      85              :  */
      86            5 : static int setup_stub(PtyTelStub *stub) {
      87              :     char tmp[256];
      88            5 :     snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-pty-ctrlc-%d", (int)getpid());
      89              : 
      90            5 :     mkdir(tmp, 0700);
      91              :     char dot_config[512];
      92            5 :     snprintf(dot_config, sizeof(dot_config), "%s/.config", tmp);
      93            5 :     mkdir(dot_config, 0700);
      94              :     char cfg_dir[640];
      95            5 :     snprintf(cfg_dir, sizeof(cfg_dir), "%s/tg-cli", dot_config);
      96            5 :     mkdir(cfg_dir, 0700);
      97              : 
      98              :     char ini_path[768];
      99            5 :     snprintf(ini_path, sizeof(ini_path), "%s/config.ini", cfg_dir);
     100            5 :     FILE *f = fopen(ini_path, "w");
     101            5 :     if (!f) return 0;
     102            5 :     fprintf(f, "api_id=12345\napi_hash=deadbeefcafebabef00dbaadfeedc0de\n");
     103            5 :     fclose(f);
     104              : 
     105            5 :     setenv("HOME", tmp, 1);
     106              : 
     107            5 :     if (pty_tel_stub_start(stub) != 0) return 0;
     108              : 
     109            5 :     setenv("TG_CLI_DC_HOST", "127.0.0.1", 1);
     110              :     char port_str[16];
     111            5 :     snprintf(port_str, sizeof(port_str), "%d", stub->port);
     112            5 :     setenv("TG_CLI_DC_PORT", port_str, 1);
     113              : 
     114            5 :     setenv("TG_CLI_API_ID",   "12345", 1);
     115            5 :     setenv("TG_CLI_API_HASH", "deadbeefcafebabef00dbaadfeedc0de", 1);
     116              : 
     117            5 :     return 1;
     118              : }
     119              : 
     120              : /* ── Tests ───────────────────────────────────────────────────────────── */
     121              : 
     122              : /**
     123              :  * @brief PTY-05-a: REPL mode — Ctrl-C on the prompt exits cleanly (code 0).
     124              :  *
     125              :  * In REPL mode the terminal stays in cooked mode; rl_readline intercepts
     126              :  * Ctrl-C internally (raw mode is only for the custom readline widget) and
     127              :  * returns -1, which causes repl() to return 0 (clean exit).
     128              :  */
     129            3 : static void test_repl_ctrl_c(void) {
     130              :     PtyTelStub stub;
     131            3 :     CHECK(setup_stub(&stub), "setup_stub failed", done_no_stub);
     132              : 
     133            3 :     PtySession *s = pty_open(80, 24);
     134            3 :     CHECK(s != NULL, "pty_open failed", done_no_session);
     135              : 
     136            3 :     const char *argv[] = { TG_TUI_BINARY, NULL };
     137            3 :     CHECK(pty_run(s, argv) == 0, "pty_run(tg-tui) failed", done);
     138              : 
     139              :     /* Wait for the REPL prompt. */
     140            2 :     CHECK_WAIT_FOR(s, "tg>", 5000, done);
     141              : 
     142              :     /* Send Ctrl-C. */
     143            2 :     pty_send_key(s, PTY_KEY_CTRL_C);
     144              : 
     145              :     /* The process should exit gracefully with code 0. */
     146            2 :     int code = pty_wait_exit(s, 2000);
     147            2 :     g_tests_run++;
     148            2 :     if (code != 0) {
     149            0 :         printf("  [FAIL] %s:%d: REPL Ctrl-C exit code: expected 0, got %d\n",
     150              :                __FILE__, __LINE__, code);
     151            0 :         g_tests_failed++;
     152              :     }
     153              : 
     154              :     /* PTY dimensions must be intact (terminal not left garbled). */
     155            2 :     int cols = 0, rows = 0;
     156            2 :     pty_get_size(s, &cols, &rows);
     157            2 :     g_tests_run++;
     158            2 :     if (cols != 80 || rows != 24) {
     159            0 :         printf("  [FAIL] %s:%d: PTY dimensions wrong after exit (%dx%d)\n",
     160              :                __FILE__, __LINE__, cols, rows);
     161            0 :         g_tests_failed++;
     162              :     }
     163              : 
     164            2 : done:
     165            2 :     pty_close(s);
     166            2 : done_no_session:
     167            2 :     pty_tel_stub_stop(&stub);
     168            2 : done_no_stub:
     169            2 :     return;
     170              : }
     171              : 
     172              : /**
     173              :  * @brief PTY-05-b: TUI mode (--tui) — Ctrl-C exits and restores the terminal.
     174              :  *
     175              :  * In TUI mode the terminal is in raw mode.  terminal_install_cleanup_handlers()
     176              :  * installs a SIGINT handler that restores the terminal and re-raises the signal
     177              :  * so the exit status is 130 (128+2).  We also accept 0 in case the signal is
     178              :  * delivered while the TUI loop is processing a normal quit path.
     179              :  */
     180            2 : static void test_tui_mode_ctrl_c(void) {
     181              :     PtyTelStub stub;
     182            2 :     CHECK(setup_stub(&stub), "setup_stub failed", done_no_stub);
     183              : 
     184            2 :     PtySession *s = pty_open(80, 24);
     185            2 :     CHECK(s != NULL, "pty_open failed", done_no_session);
     186              : 
     187            2 :     const char *argv[] = { TG_TUI_BINARY, "--tui", NULL };
     188            2 :     CHECK(pty_run(s, argv) == 0, "pty_run(tg-tui --tui) failed", done);
     189              : 
     190              :     /* Wait for the dialog-pane hint in the status row. */
     191            1 :     CHECK_WAIT_FOR(s, "[dialogs]", 5000, done);
     192              : 
     193              :     /* Send Ctrl-C to the TUI. */
     194            1 :     pty_send_key(s, PTY_KEY_CTRL_C);
     195              : 
     196              :     /* Allow up to 2 seconds for the process to exit. */
     197            1 :     int code = pty_wait_exit(s, 2000);
     198              : 
     199              :     /* Accept 130 (SIGINT with restored-then-re-raised handler) or 0 (graceful). */
     200            1 :     g_tests_run++;
     201            1 :     if (code != 130 && code != 0) {
     202            0 :         printf("  [FAIL] %s:%d: TUI Ctrl-C exit code: expected 130 or 0, got %d\n",
     203              :                __FILE__, __LINE__, code);
     204            0 :         g_tests_failed++;
     205              :     }
     206              : 
     207              :     /* PTY dimensions must be intact (terminal not left in raw mode). */
     208            1 :     int cols = 0, rows = 0;
     209            1 :     pty_get_size(s, &cols, &rows);
     210            1 :     g_tests_run++;
     211            1 :     if (cols != 80 || rows != 24) {
     212            0 :         printf("  [FAIL] %s:%d: PTY dimensions wrong after exit (%dx%d)\n",
     213              :                __FILE__, __LINE__, cols, rows);
     214            0 :         g_tests_failed++;
     215              :     }
     216              : 
     217            1 : done:
     218            1 :     pty_close(s);
     219            1 : done_no_session:
     220            1 :     pty_tel_stub_stop(&stub);
     221            1 : done_no_stub:
     222            1 :     return;
     223              : }
     224              : 
     225              : /* ── Entry point ─────────────────────────────────────────────────────── */
     226              : 
     227            3 : int main(void) {
     228            3 :     printf("PTY-05 Ctrl-C exit tests\n");
     229              : 
     230            3 :     RUN_TEST(test_repl_ctrl_c);
     231            2 :     RUN_TEST(test_tui_mode_ctrl_c);
     232              : 
     233            1 :     printf("\n%d tests run, %d failed\n", g_tests_run, g_tests_failed);
     234            1 :     return g_tests_failed > 0 ? 1 : 0;
     235              : }
        

Generated by: LCOV version 2.0-1