LCOV - code coverage report
Current view: top level - tests/unit - test_platform.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 97.3 % 75 73
Test Date: 2026-05-07 15:53:07 Functions: 100.0 % 1 1

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "platform/terminal.h"
       3              : #include "platform/path.h"
       4              : #include <stdlib.h>
       5              : #include <string.h>
       6              : #include <unistd.h>
       7              : #include <fcntl.h>
       8              : #include <locale.h>
       9              : 
      10            1 : void test_platform(void) {
      11              : 
      12              :     /* ── terminal_wcwidth ───────────────────────────────────────────── */
      13              : 
      14            1 :     setlocale(LC_ALL, "");
      15              : 
      16              :     /* ASCII printable characters: width 1 */
      17            1 :     ASSERT(terminal_wcwidth('A')    == 1, "wcwidth: ASCII letter should be 1");
      18            1 :     ASSERT(terminal_wcwidth(' ')    == 1, "wcwidth: space should be 1");
      19            1 :     ASSERT(terminal_wcwidth('0')    == 1, "wcwidth: digit should be 1");
      20              : 
      21              :     /* Control characters: width 0 (not negative) */
      22            1 :     ASSERT(terminal_wcwidth('\n')   == 0, "wcwidth: newline should be 0");
      23            1 :     ASSERT(terminal_wcwidth('\t')   == 0, "wcwidth: tab should be 0");
      24            1 :     ASSERT(terminal_wcwidth(0x01)   == 0, "wcwidth: control char should be 0");
      25              : 
      26              :     /* Common accented Latin characters: width 1 */
      27            1 :     ASSERT(terminal_wcwidth(0x00E9) == 1, "wcwidth: e-acute (U+00E9) should be 1");
      28            1 :     ASSERT(terminal_wcwidth(0x00E1) == 1, "wcwidth: a-acute (U+00E1) should be 1");
      29            1 :     ASSERT(terminal_wcwidth(0x0151) == 1, "wcwidth: o-double-acute (U+0151) should be 1");
      30              : 
      31              :     /* CJK ideograph: width 2 */
      32            1 :     ASSERT(terminal_wcwidth(0x4E2D) == 2, "wcwidth: CJK U+4E2D should be 2");
      33            1 :     ASSERT(terminal_wcwidth(0x3042) == 2, "wcwidth: Hiragana U+3042 should be 2");
      34              : 
      35              :     /* ── terminal_is_tty ────────────────────────────────────────────── */
      36              : 
      37              :     /* When stdout is a pipe (as in test runner), fd 1 is not a tty */
      38              :     /* We cannot assert a specific value since it depends on the test
      39              :      * environment, but the function must return 0 or 1 without crashing. */
      40            1 :     int r = terminal_is_tty(STDOUT_FILENO);
      41            1 :     ASSERT(r == 0 || r == 1, "terminal_is_tty must return 0 or 1");
      42              : 
      43              :     /* Invalid fd must return 0 */
      44            1 :     ASSERT(terminal_is_tty(-1) == 0, "terminal_is_tty(-1) should return 0");
      45            1 :     ASSERT(terminal_is_tty(9999) == 0, "terminal_is_tty(9999) should return 0");
      46              : 
      47              :     /* ── terminal_cols ──────────────────────────────────────────────── */
      48              : 
      49              :     /* When stdout is not a tty (e.g. piped in CI), must fall back to 80. */
      50            1 :     if (!terminal_is_tty(STDOUT_FILENO)) {
      51            1 :         ASSERT(terminal_cols() == 80,
      52              :                "terminal_cols() should return 80 when stdout is not a tty");
      53              :     } else {
      54              :         /* On a real terminal it must be a positive value. */
      55            0 :         ASSERT(terminal_cols() > 0, "terminal_cols() must be positive");
      56              :     }
      57              : 
      58              :     /* ── terminal_rows ──────────────────────────────────────────────── */
      59              : 
      60            1 :     int rows = terminal_rows();
      61            1 :     if (!terminal_is_tty(STDOUT_FILENO)) {
      62            1 :         ASSERT(rows == 0, "terminal_rows() should return 0 when stdout is not a tty");
      63              :     } else {
      64            0 :         ASSERT(rows > 0, "terminal_rows() must be positive on a real terminal");
      65              :     }
      66              : 
      67              :     /* ── terminal_raw_enter / terminal_raw_exit ─────────────────────── */
      68              : 
      69              :     /* When stdin is not a tty (as in test runner), raw_enter must return NULL
      70              :      * gracefully (tcgetattr will fail). */
      71            1 :     if (!terminal_is_tty(STDIN_FILENO)) {
      72            1 :         TermRawState *s = terminal_raw_enter();
      73            1 :         ASSERT(s == NULL,
      74              :                "terminal_raw_enter should return NULL when stdin is not a tty");
      75              :         /* terminal_raw_exit(NULL) and terminal_raw_exit(&NULL) must be safe. */
      76            1 :         terminal_raw_exit(NULL);
      77            1 :         terminal_raw_exit(&s);   /* s is already NULL */
      78              :     }
      79              : 
      80              :     /* ── terminal_read_password — guard clauses ─────────────────────── */
      81              : 
      82              :     char pwbuf[64];
      83              :     /* NULL buf → -1 */
      84            1 :     ASSERT(terminal_read_password("test", NULL, 64) == -1,
      85              :            "terminal_read_password: NULL buf should return -1");
      86              :     /* size == 0 → -1 */
      87            1 :     ASSERT(terminal_read_password("test", pwbuf, 0) == -1,
      88              :            "terminal_read_password: size 0 should return -1");
      89              : 
      90              :     /* Non-tty stdin path: getline on an empty/closed stream returns -1. */
      91            1 :     if (!terminal_is_tty(STDIN_FILENO)) {
      92            1 :         int n = terminal_read_password("test", pwbuf, sizeof(pwbuf));
      93            1 :         ASSERT(n == -1 || n >= 0,
      94              :                "terminal_read_password non-tty: must not crash");
      95              :     }
      96              : 
      97              :     /* Non-tty stdin with CRLF input: covers the \\r stripping step
      98              :      * (terminal.c line 214: if (slen > 0 && line[slen-1] == '\\r')). */
      99            1 :     if (!terminal_is_tty(STDIN_FILENO)) {
     100              :         int pfd[2];
     101            1 :         if (pipe(pfd) == 0) {
     102            1 :             const char *crlf_input = "secret\r\n";
     103            1 :             ssize_t _wr = write(pfd[1], crlf_input, 8); (void)_wr;
     104            1 :             close(pfd[1]);
     105            1 :             int saved = dup(STDIN_FILENO);
     106            1 :             dup2(pfd[0], STDIN_FILENO);
     107            1 :             close(pfd[0]);
     108            1 :             clearerr(stdin); /* clear EOF from any previous non-tty read */
     109              :             char pwbuf2[64];
     110            1 :             int n2 = terminal_read_password("Password", pwbuf2, sizeof(pwbuf2));
     111            1 :             dup2(saved, STDIN_FILENO);
     112            1 :             close(saved);
     113            1 :             ASSERT(n2 == 6 && strcmp(pwbuf2, "secret") == 0,
     114              :                    "terminal_read_password: CRLF input strips \\r");
     115              :         }
     116              :     }
     117              : 
     118              :     /* ── platform_home_dir ──────────────────────────────────────────── */
     119              : 
     120            1 :     const char *home = platform_home_dir();
     121            1 :     ASSERT(home != NULL, "platform_home_dir should not return NULL");
     122            1 :     ASSERT(home[0] == '/', "platform_home_dir should return an absolute path");
     123              : 
     124              :     /* Must still work when HOME is unset (getpwuid fallback) */
     125            1 :     char saved_home[4096] = {0};
     126            1 :     const char *env_home = getenv("HOME");
     127            1 :     if (env_home) snprintf(saved_home, sizeof(saved_home), "%s", env_home);
     128            1 :     unsetenv("HOME");
     129            1 :     home = platform_home_dir();
     130            1 :     ASSERT(home != NULL, "platform_home_dir should fall back to getpwuid");
     131            1 :     if (saved_home[0]) setenv("HOME", saved_home, 1);
     132              : 
     133              :     /* ── platform_cache_dir ─────────────────────────────────────────── */
     134              : 
     135              :     /* Default: ~/.cache */
     136            1 :     unsetenv("XDG_CACHE_HOME");
     137            1 :     const char *cache = platform_cache_dir();
     138            1 :     ASSERT(cache != NULL, "platform_cache_dir should not return NULL");
     139            1 :     ASSERT(strstr(cache, ".cache") != NULL,
     140              :            "platform_cache_dir default should contain '.cache'");
     141              : 
     142              :     /* XDG override */
     143            1 :     setenv("XDG_CACHE_HOME", "/tmp/test-xdg-cache", 1);
     144            1 :     cache = platform_cache_dir();
     145            1 :     ASSERT(cache != NULL, "platform_cache_dir XDG should not return NULL");
     146            1 :     ASSERT(strcmp(cache, "/tmp/test-xdg-cache") == 0,
     147              :            "platform_cache_dir should respect XDG_CACHE_HOME");
     148            1 :     unsetenv("XDG_CACHE_HOME");
     149              : 
     150              :     /* ── platform_config_dir ────────────────────────────────────────── */
     151              : 
     152              :     /* Default: ~/.config */
     153            1 :     unsetenv("XDG_CONFIG_HOME");
     154            1 :     const char *cfg = platform_config_dir();
     155            1 :     ASSERT(cfg != NULL, "platform_config_dir should not return NULL");
     156            1 :     ASSERT(strstr(cfg, ".config") != NULL,
     157              :            "platform_config_dir default should contain '.config'");
     158              : 
     159              :     /* XDG override */
     160            1 :     setenv("XDG_CONFIG_HOME", "/tmp/test-xdg-config", 1);
     161            1 :     cfg = platform_config_dir();
     162            1 :     ASSERT(cfg != NULL, "platform_config_dir XDG should not return NULL");
     163            1 :     ASSERT(strcmp(cfg, "/tmp/test-xdg-config") == 0,
     164              :            "platform_config_dir should respect XDG_CONFIG_HOME");
     165            1 :     unsetenv("XDG_CONFIG_HOME");
     166              : }
        

Generated by: LCOV version 2.0-1