LCOV - code coverage report
Current view: top level - tests/unit - test_platform.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.8 % 63 61
Test Date: 2026-04-15 21:12:52 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 <locale.h>
       8              : 
       9            1 : void test_platform(void) {
      10              : 
      11              :     /* ── terminal_wcwidth ───────────────────────────────────────────── */
      12              : 
      13            1 :     setlocale(LC_ALL, "");
      14              : 
      15              :     /* ASCII printable characters: width 1 */
      16            1 :     ASSERT(terminal_wcwidth('A')    == 1, "wcwidth: ASCII letter should be 1");
      17            1 :     ASSERT(terminal_wcwidth(' ')    == 1, "wcwidth: space should be 1");
      18            1 :     ASSERT(terminal_wcwidth('0')    == 1, "wcwidth: digit should be 1");
      19              : 
      20              :     /* Control characters: width 0 (not negative) */
      21            1 :     ASSERT(terminal_wcwidth('\n')   == 0, "wcwidth: newline should be 0");
      22            1 :     ASSERT(terminal_wcwidth('\t')   == 0, "wcwidth: tab should be 0");
      23            1 :     ASSERT(terminal_wcwidth(0x01)   == 0, "wcwidth: control char should be 0");
      24              : 
      25              :     /* Common accented Latin characters: width 1 */
      26            1 :     ASSERT(terminal_wcwidth(0x00E9) == 1, "wcwidth: e-acute (U+00E9) should be 1");
      27            1 :     ASSERT(terminal_wcwidth(0x00E1) == 1, "wcwidth: a-acute (U+00E1) should be 1");
      28            1 :     ASSERT(terminal_wcwidth(0x0151) == 1, "wcwidth: o-double-acute (U+0151) should be 1");
      29              : 
      30              :     /* CJK ideograph: width 2 */
      31            1 :     ASSERT(terminal_wcwidth(0x4E2D) == 2, "wcwidth: CJK U+4E2D should be 2");
      32            1 :     ASSERT(terminal_wcwidth(0x3042) == 2, "wcwidth: Hiragana U+3042 should be 2");
      33              : 
      34              :     /* ── terminal_is_tty ────────────────────────────────────────────── */
      35              : 
      36              :     /* When stdout is a pipe (as in test runner), fd 1 is not a tty */
      37              :     /* We cannot assert a specific value since it depends on the test
      38              :      * environment, but the function must return 0 or 1 without crashing. */
      39            1 :     int r = terminal_is_tty(STDOUT_FILENO);
      40            1 :     ASSERT(r == 0 || r == 1, "terminal_is_tty must return 0 or 1");
      41              : 
      42              :     /* Invalid fd must return 0 */
      43            1 :     ASSERT(terminal_is_tty(-1) == 0, "terminal_is_tty(-1) should return 0");
      44            1 :     ASSERT(terminal_is_tty(9999) == 0, "terminal_is_tty(9999) should return 0");
      45              : 
      46              :     /* ── terminal_cols ──────────────────────────────────────────────── */
      47              : 
      48              :     /* When stdout is not a tty (e.g. piped in CI), must fall back to 80. */
      49            1 :     if (!terminal_is_tty(STDOUT_FILENO)) {
      50            1 :         ASSERT(terminal_cols() == 80,
      51              :                "terminal_cols() should return 80 when stdout is not a tty");
      52              :     } else {
      53              :         /* On a real terminal it must be a positive value. */
      54            0 :         ASSERT(terminal_cols() > 0, "terminal_cols() must be positive");
      55              :     }
      56              : 
      57              :     /* ── terminal_rows ──────────────────────────────────────────────── */
      58              : 
      59            1 :     int rows = terminal_rows();
      60            1 :     if (!terminal_is_tty(STDOUT_FILENO)) {
      61            1 :         ASSERT(rows == 0, "terminal_rows() should return 0 when stdout is not a tty");
      62              :     } else {
      63            0 :         ASSERT(rows > 0, "terminal_rows() must be positive on a real terminal");
      64              :     }
      65              : 
      66              :     /* ── terminal_raw_enter / terminal_raw_exit ─────────────────────── */
      67              : 
      68              :     /* When stdin is not a tty (as in test runner), raw_enter must return NULL
      69              :      * gracefully (tcgetattr will fail). */
      70            1 :     if (!terminal_is_tty(STDIN_FILENO)) {
      71            1 :         TermRawState *s = terminal_raw_enter();
      72            1 :         ASSERT(s == NULL,
      73              :                "terminal_raw_enter should return NULL when stdin is not a tty");
      74              :         /* terminal_raw_exit(NULL) and terminal_raw_exit(&NULL) must be safe. */
      75            1 :         terminal_raw_exit(NULL);
      76            1 :         terminal_raw_exit(&s);   /* s is already NULL */
      77              :     }
      78              : 
      79              :     /* ── terminal_read_password — guard clauses ─────────────────────── */
      80              : 
      81            1 :     char pwbuf[64];
      82              :     /* NULL buf → -1 */
      83            1 :     ASSERT(terminal_read_password("test", NULL, 64) == -1,
      84              :            "terminal_read_password: NULL buf should return -1");
      85              :     /* size == 0 → -1 */
      86            1 :     ASSERT(terminal_read_password("test", pwbuf, 0) == -1,
      87              :            "terminal_read_password: size 0 should return -1");
      88              : 
      89              :     /* Non-tty stdin path: getline on an empty/closed stream returns -1. */
      90            1 :     if (!terminal_is_tty(STDIN_FILENO)) {
      91            1 :         int n = terminal_read_password("test", pwbuf, sizeof(pwbuf));
      92            1 :         ASSERT(n == -1 || n >= 0,
      93              :                "terminal_read_password non-tty: must not crash");
      94              :     }
      95              : 
      96              :     /* ── platform_home_dir ──────────────────────────────────────────── */
      97              : 
      98            1 :     const char *home = platform_home_dir();
      99            1 :     ASSERT(home != NULL, "platform_home_dir should not return NULL");
     100            1 :     ASSERT(home[0] == '/', "platform_home_dir should return an absolute path");
     101              : 
     102              :     /* Must still work when HOME is unset (getpwuid fallback) */
     103            1 :     char saved_home[4096] = {0};
     104            1 :     const char *env_home = getenv("HOME");
     105            1 :     if (env_home) snprintf(saved_home, sizeof(saved_home), "%s", env_home);
     106            1 :     unsetenv("HOME");
     107            1 :     home = platform_home_dir();
     108            1 :     ASSERT(home != NULL, "platform_home_dir should fall back to getpwuid");
     109            1 :     if (saved_home[0]) setenv("HOME", saved_home, 1);
     110              : 
     111              :     /* ── platform_cache_dir ─────────────────────────────────────────── */
     112              : 
     113              :     /* Default: ~/.cache */
     114            1 :     unsetenv("XDG_CACHE_HOME");
     115            1 :     const char *cache = platform_cache_dir();
     116            1 :     ASSERT(cache != NULL, "platform_cache_dir should not return NULL");
     117            1 :     ASSERT(strstr(cache, ".cache") != NULL,
     118              :            "platform_cache_dir default should contain '.cache'");
     119              : 
     120              :     /* XDG override */
     121            1 :     setenv("XDG_CACHE_HOME", "/tmp/test-xdg-cache", 1);
     122            1 :     cache = platform_cache_dir();
     123            1 :     ASSERT(cache != NULL, "platform_cache_dir XDG should not return NULL");
     124            1 :     ASSERT(strcmp(cache, "/tmp/test-xdg-cache") == 0,
     125              :            "platform_cache_dir should respect XDG_CACHE_HOME");
     126            1 :     unsetenv("XDG_CACHE_HOME");
     127              : 
     128              :     /* ── platform_config_dir ────────────────────────────────────────── */
     129              : 
     130              :     /* Default: ~/.config */
     131            1 :     unsetenv("XDG_CONFIG_HOME");
     132            1 :     const char *cfg = platform_config_dir();
     133            1 :     ASSERT(cfg != NULL, "platform_config_dir should not return NULL");
     134            1 :     ASSERT(strstr(cfg, ".config") != NULL,
     135              :            "platform_config_dir default should contain '.config'");
     136              : 
     137              :     /* XDG override */
     138            1 :     setenv("XDG_CONFIG_HOME", "/tmp/test-xdg-config", 1);
     139            1 :     cfg = platform_config_dir();
     140            1 :     ASSERT(cfg != NULL, "platform_config_dir XDG should not return NULL");
     141            1 :     ASSERT(strcmp(cfg, "/tmp/test-xdg-config") == 0,
     142              :            "platform_config_dir should respect XDG_CONFIG_HOME");
     143            1 :     unsetenv("XDG_CONFIG_HOME");
     144              : }
        

Generated by: LCOV version 2.0-1