master ohmu/fastfetch-openbsd / tests / strutil.c
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}