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