/* cst_fnmatch and the os_dir / os_lstat layer that replaced the POSIX-only * directory code; runs on every platform. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "cst_util.h" #include "os_compat.h" #include #include #include #include "cst_test_util.h" static int nfail; #define EXPECT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) static void fnm(const char *pat, const char *s, int flags, int want) { int got = cst_fnmatch(pat, s, flags) == 0; if (got != want) { fprintf(stderr, "FAIL fnmatch(\"%s\", \"%s\", %d) = %d\n", pat, s, flags, got); nfail++; } } static void test_fnmatch(void) { fnm("*.c", "main.c", 0, 1); fnm("*.c", "main.h", 0, 0); fnm("*.c", "src/main.c", 0, 1); /* '*' crosses '/' */ fnm("?.txt", "a.txt", 0, 1); fnm("?.txt", "ab.txt", 0, 0); fnm("?.txt", "中.txt", 0, 1); /* one code point */ fnm("[abc]x", "bx", 0, 1); fnm("[!abc]x", "bx", 0, 0); fnm("[a-c]x", "cx", 0, 1); fnm("[[:digit:]]*", "7up", 0, 1); fnm("[[:digit:]]*", "up", 0, 0); fnm("\\*", "*", 0, 1); fnm("\\*", "a", 0, 0); fnm("*", ".hidden", CST_FNM_PERIOD, 0); fnm(".*", ".hidden", CST_FNM_PERIOD, 1); fnm("*", ".hidden", 0, 1); fnm("*文件*", "我的文件.txt", 0, 1); fnm("README*", "readme.md", CST_FNM_CASEFOLD, 1); #ifdef _WIN32 fnm("README*", "readme.md", 0, 1); /* Windows names are case-insensitive */ #else fnm("README*", "readme.md", 0, 0); #endif fnm("", "", 0, 1); fnm("a", "", 0, 0); } static void touch(const char *dir, const char *name) { char p[1200]; snprintf(p, sizeof(p), "%s/%s", dir, name); FILE *f = fopen(p, "wb"); if (f) { fputs("x", f); fclose(f); } } static void test_dir(void) { char root[1024], sub[1100], p[1200]; if (test_tmpdir(root, sizeof(root), "cst-osdir") != 0) { EXPECT(0, "tmpdir"); return; } snprintf(sub, sizeof(sub), "%s/子目录", root); EXPECT(test_mkdir(sub, 0755) == 0, "mkdir with a Chinese name"); touch(root, "a.txt"); touch(root, "文件 一.md"); touch(root, ".hidden"); int files = 0, dirs = 0, seen_cn = 0, seen_hidden = 0, dots = 0; os_dir_t *d = os_dir_open(root); EXPECT(d != NULL, "os_dir_open"); const os_dirent_t *e; while (d && (e = os_dir_next(d)) != NULL) { if (!strcmp(e->name, ".") || !strcmp(e->name, "..")) dots++; if (e->is_dir) dirs++; else files++; seen_cn |= strcmp(e->name, "文件 一.md") == 0 && !e->is_dir; seen_hidden |= strcmp(e->name, ".hidden") == 0; if (!strcmp(e->name, "子目录")) EXPECT(e->is_dir && !e->is_link, "subdir flags"); } os_dir_close(d); EXPECT(dots == 0, "no . or .. entries"); EXPECT(files == 3 && dirs == 1, "entry counts"); EXPECT(seen_cn, "UTF-8 file name listed"); EXPECT(seen_hidden, "dot files listed"); d = os_dir_open(sub); EXPECT(d != NULL && os_dir_next(d) == NULL, "empty Chinese subdir"); if (d) os_dir_close(d); os_stat_t st; snprintf(p, sizeof(p), "%s/文件 一.md", root); EXPECT(os_lstat(p, &st) == 0 && st.is_reg && !st.is_dir && st.size == 1, "os_lstat on a UTF-8 file"); EXPECT(os_lstat(sub, &st) == 0 && st.is_dir, "os_lstat on a dir"); snprintf(p, sizeof(p), "%s/missing", root); EXPECT(os_lstat(p, &st) != 0, "os_lstat on a missing path"); EXPECT(os_dir_open(p) == NULL, "os_dir_open on a missing path"); char real[1200], rr[1200]; EXPECT(os_realpath(sub, real, sizeof(real)) == 0 && os_realpath(root, rr, sizeof(rr)) == 0, "os_realpath"); EXPECT(os_path_under(rr, real), "realpath of subdir is under root"); EXPECT(!os_path_under(real, rr), "root is not under subdir"); snprintf(p, sizeof(p), "%s/link", root); int lk = test_symlink(sub, p); if (lk == 0) { EXPECT(os_lstat(p, &st) == 0 && st.is_link, "symlink is reported as link"); } else { printf("test_os_dir: symlinks not permitted, link checks skipped\n"); } test_rmrf(root); EXPECT(os_lstat(root, &st) != 0, "test_rmrf removed everything"); } int main(void) { os_console_init(); test_fnmatch(); test_dir(); if (nfail) { fprintf(stderr, "test_os_dir: %d failure(s)\n", nfail); return 1; } puts("test_os_dir: OK"); return 0; }