ohmu
·
2026-10-15
1#include "common/strutil.h"
2#include "common/textModifier.h"
3
4#include <stdint.h>
5#include <stdio.h>
6#include <stdlib.h>
7
8static void verify(bool expression, const char* expressionStr, int lineNo) {
9 if (expression) {
10 return;
11 }
12
13 fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expressionStr);
14 exit(1);
15}
16
17#define VERIFY(expression) verify((expression), #expression, __LINE__)
18
19int main(void) {
20 // The plain C string helpers. These have nothing to do with wcwidth, so they run in every build.
21 {
22 VERIFY(!ffStrSet(nullptr));
23 VERIFY(!ffStrSet(""));
24 VERIFY(!ffStrSet(" "));
25 VERIFY(!ffStrSet(" \t \n"));
26 VERIFY(ffStrSet("a"));
27 VERIFY(ffStrSet(" a"));
28 VERIFY(ffStrSet("a "));
29 }
30
31 {
32 VERIFY(ffStrStartsWith("", ""));
33 VERIFY(ffStrStartsWith("abc", ""));
34 VERIFY(ffStrStartsWith("abc", "a"));
35 VERIFY(ffStrStartsWith("abc", "abc"));
36 VERIFY(!ffStrStartsWith("abc", "abcd")); // the needle is longer than the haystack
37 VERIFY(!ffStrStartsWith("abc", "b"));
38 VERIFY(!ffStrStartsWith("", "a"));
39 VERIFY(!ffStrStartsWith("Abc", "abc"));
40
41 VERIFY(ffStrStartsWithIgnCase("Abc", "abc"));
42 VERIFY(ffStrStartsWithIgnCase("ABC", "abc"));
43 VERIFY(ffStrStartsWithIgnCase("abc", ""));
44 VERIFY(!ffStrStartsWithIgnCase("Abc", "abd"));
45 }
46
47 {
48 VERIFY(ffStrEndsWith("", ""));
49 VERIFY(ffStrEndsWith("abc", ""));
50 VERIFY(ffStrEndsWith("abc", "c"));
51 VERIFY(ffStrEndsWith("abc", "abc"));
52 VERIFY(!ffStrEndsWith("abc", "abcd")); // the needle is longer than the haystack
53 VERIFY(!ffStrEndsWith("abc", "b"));
54 VERIFY(!ffStrEndsWith("", "a"));
55 VERIFY(!ffStrEndsWith("abc", "Abc"));
56
57 VERIFY(ffStrEndsWithIgnCase("abc", "BC"));
58 VERIFY(ffStrEndsWithIgnCase("abc", ""));
59 VERIFY(!ffStrEndsWithIgnCase("abc", "BD"));
60 }
61
62 {
63 VERIFY(ffStrEquals("", ""));
64 VERIFY(ffStrEquals("abc", "abc"));
65 VERIFY(!ffStrEquals("abc", "abcd"));
66 VERIFY(!ffStrEquals("abc", "ABC"));
67
68 VERIFY(ffStrEqualsIgnCase("abc", "ABC"));
69 VERIFY(ffStrEqualsIgnCase("", ""));
70 VERIFY(!ffStrEqualsIgnCase("abc", "abd"));
71
72 VERIFY(ffStrContains("abc", ""));
73 VERIFY(ffStrContains("abc", "b"));
74 VERIFY(ffStrContains("abc", "abc"));
75 VERIFY(!ffStrContains("abc", "d"));
76
77 VERIFY(ffStrContainsIgnCase("abc", "B"));
78 VERIFY(!ffStrContainsIgnCase("abc", "D"));
79 // `strcasestr` reports an empty needle as a match, but the `StrStrIA` it is aliased to on
80 // Windows reports no match, so on Windows this disagrees with `ffStrContains`.
81#ifndef _WIN32
82 VERIFY(ffStrContainsIgnCase("abc", ""));
83#endif
84
85 VERIFY(ffStrContainsC("abc", 'b'));
86 VERIFY(!ffStrContainsC("abc", 'd'));
87 VERIFY(ffStrContainsC("abc", '\0')); // `strchr` reports the terminator as a match
88 }
89
90 {
91 VERIFY(ffCharIsEnglishAlphabet('a'));
92 VERIFY(ffCharIsEnglishAlphabet('z'));
93 VERIFY(ffCharIsEnglishAlphabet('A'));
94 VERIFY(ffCharIsEnglishAlphabet('Z'));
95 VERIFY(!ffCharIsEnglishAlphabet('0'));
96 VERIFY(!ffCharIsEnglishAlphabet('_'));
97 VERIFY(!ffCharIsEnglishAlphabet(' '));
98
99 VERIFY(ffCharIsDigit('0'));
100 VERIFY(ffCharIsDigit('9'));
101 VERIFY(!ffCharIsDigit('/')); // one below '0'
102 VERIFY(!ffCharIsDigit(':')); // one above '9'
103 VERIFY(!ffCharIsDigit('a'));
104
105 VERIFY(ffCharIsHexDigit('0'));
106 VERIFY(ffCharIsHexDigit('9'));
107 VERIFY(ffCharIsHexDigit('a'));
108 VERIFY(ffCharIsHexDigit('F'));
109 VERIFY(!ffCharIsHexDigit('g'));
110 VERIFY(!ffCharIsHexDigit('G'));
111 VERIFY(!ffCharIsHexDigit('/'));
112 VERIFY(!ffCharIsHexDigit('@'));
113 }
114
115 {
116 VERIFY(ffHexCharToInt('0') == 0);
117 VERIFY(ffHexCharToInt('9') == 9);
118 VERIFY(ffHexCharToInt('a') == 10);
119 VERIFY(ffHexCharToInt('f') == 15);
120 VERIFY(ffHexCharToInt('A') == 10);
121 VERIFY(ffHexCharToInt('F') == 15);
122 VERIFY(ffHexCharToInt('g') == -1);
123 VERIFY(ffHexCharToInt('G') == -1);
124 VERIFY(ffHexCharToInt('/') == -1); // one below '0'
125 VERIFY(ffHexCharToInt(':') == -1); // one above '9'
126 VERIFY(ffHexCharToInt('@') == -1); // one below 'A'
127 VERIFY(ffHexCharToInt('`') == -1); // one below 'a'
128 VERIFY(ffHexCharToInt(' ') == -1);
129 }
130
131 // ffStrCopy never writes more than `dstBufSiz` bytes and always terminates. It returns a pointer
132 // to the end of the copy, so consecutive calls can be chained.
133 {
134 char buffer[8];
135
136 memset(buffer, 'X', sizeof(buffer));
137 char* end = ffStrCopy(buffer, "abc", sizeof(buffer));
138 VERIFY(ffStrEquals(buffer, "abc"));
139 VERIFY(end == buffer + 3);
140
141 memset(buffer, 'X', sizeof(buffer));
142 end = ffStrCopy(buffer, "abcdef", sizeof(buffer));
143 VERIFY(ffStrEquals(buffer, "abcdef"));
144 VERIFY(end == buffer + 6);
145
146 // Filling the buffer exactly still leaves room for the terminator
147 char exact[4];
148 memset(exact, 'X', sizeof(exact));
149 end = ffStrCopy(exact, "abc", sizeof(exact));
150 VERIFY(ffStrEquals(exact, "abc"));
151 VERIFY(end == exact + 3);
152
153 // One byte short: the copy is truncated to `dstBufSiz - 1` bytes
154 char truncated[3];
155 memset(truncated, 'X', sizeof(truncated));
156 end = ffStrCopy(truncated, "abcdef", sizeof(truncated));
157 VERIFY(ffStrEquals(truncated, "ab"));
158 VERIFY(end == truncated + 2);
159
160 // A one byte buffer holds nothing but the terminator
161 char single[1];
162 single[0] = 'X';
163 end = ffStrCopy(single, "abc", sizeof(single));
164 VERIFY(single[0] == '\0');
165 VERIFY(end == single);
166
167 // A zero sized buffer is left completely alone
168 char untouched[1] = { 'X' };
169 end = ffStrCopy(untouched, "abc", 0);
170 VERIFY(untouched[0] == 'X');
171 VERIFY(end == untouched);
172
173 VERIFY(ffStrCopy(nullptr, "abc", 8) == nullptr);
174 }
175
176 // FF_STR stringifies its argument
177 {
178 VERIFY(ffStrEquals(FF_STR(1), "1"));
179 VERIFY(ffStrEquals(FF_STR(123), "123"));
180 }
181
182 #if FF_ENABLE_WCWIDTH
183 {
184 uint8_t width = 255;
185 uint8_t bytes = ffUtf8CharLenWidth("", 0, &width);
186 VERIFY(bytes == 0);
187 VERIFY(width == 0);
188 }
189
190 {
191 uint8_t width = 0;
192 uint8_t bytes = ffUtf8CharLenWidth("A", 1, &width);
193 VERIFY(bytes == 1);
194 VERIFY(width == 1);
195 }
196
197 {
198 const char* ch = "\xE6\x96\x87"; // 文 U+6587
199 uint8_t width = 0;
200 uint8_t bytes = ffUtf8CharLenWidth(ch, 3, &width);
201 VERIFY(bytes == 3);
202 VERIFY(width == 2);
203 }
204
205 {
206 const char* combining = "\xCC\x81"; // ◌́ U+0301
207 uint8_t width = 0;
208 uint8_t bytes = ffUtf8CharLenWidth(combining, 2, &width);
209 VERIFY(bytes == 2);
210 VERIFY(width == 0); // Should be 1 since there's no base character
211 }
212
213 {
214 uint8_t width = 0;
215 uint8_t bytes = ffUtf8CharLenWidth("\xE6\x96\x87", 1, &width); // truncated
216 VERIFY(bytes == 1);
217 VERIFY(width == 1);
218 }
219
220 {
221 uint8_t width = 0;
222 uint8_t bytes = ffUtf8CharLenWidth("\xE6"
223 "A",
224 2,
225 &width); // invalid continuation
226 VERIFY(bytes == 1);
227 VERIFY(width == 1);
228 }
229
230 {
231 VERIFY(ffUtf8StrWidth("abc", 3) == 3);
232 }
233
234 {
235 const char* mixed = "A"
236 "\xE6\x96\x87" // 文 U+6587
237 "B";
238 VERIFY(ffUtf8StrWidth(mixed, 5) == 4);
239 }
240
241 {
242 const char* combining = "A\xCC\x81"; // Á
243 VERIFY(ffUtf8StrWidth(combining, 3) == 1);
244 }
245
246 {
247 VERIFY(ffUtf8StrWidth("\xE6"
248 "A",
249 2) == 2);
250 }
251
252 {
253 VERIFY(ffUtf8StrWidth("", 0) == 0);
254 VERIFY(ffUtf8StrWidth("A\0B", 3) == 1);
255 }
256
257 {
258 const char* emoji = "\xF0\x9F\x98\x80"; // U+1F600 😀
259 uint8_t width = 0;
260 uint8_t bytes = ffUtf8CharLenWidth(emoji, 4, &width);
261 VERIFY(bytes == 4);
262 VERIFY(width == 2);
263 }
264
265 puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
266 #else
267 puts("\033[33mwcwidth tests skipped because wcwidth support is disabled." FASTFETCH_TEXT_MODIFIER_RESET);
268 #endif
269 return 0;
270}