wibo/test/test_findfile.c

316 lines
9.9 KiB
C

#include "test_assert.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
static char g_original_dir[MAX_PATH];
static char g_fixture_dir[MAX_PATH];
static const char *leaf_name(const char *path) {
const char *back = strrchr(path, '\\');
const char *forward = strrchr(path, '/');
const char *candidate = back;
if (!candidate || (forward && forward > candidate)) {
candidate = forward;
}
if (candidate && candidate[1] != '\0') {
return candidate + 1;
}
return path;
}
static void join_path(char *buffer, size_t buffer_size, const char *a, const char *b) {
int written = snprintf(buffer, buffer_size, "%s\\%s", a, b);
TEST_CHECK_MSG(written > 0 && (size_t)written < buffer_size, "join_path overflow");
}
static void create_file_with_content(const char *path, const char *content) {
HANDLE handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK_MSG(handle != INVALID_HANDLE_VALUE, "CreateFileA(%s) failed", path);
DWORD to_write = (DWORD)strlen(content);
DWORD written = 0;
BOOL ok = WriteFile(handle, content, to_write, &written, NULL);
TEST_CHECK(ok);
TEST_CHECK_EQ(to_write, written);
TEST_CHECK(CloseHandle(handle));
}
static void setup_fixture(void) {
DWORD len = GetCurrentDirectoryA(sizeof(g_original_dir), g_original_dir);
TEST_CHECK(len > 0 && len < sizeof(g_original_dir));
char temp_path[MAX_PATH];
DWORD tmp_len = GetTempPathA(sizeof(temp_path), temp_path);
TEST_CHECK(tmp_len > 0 && tmp_len < sizeof(temp_path));
char temp_name[MAX_PATH];
UINT unique = GetTempFileNameA(temp_path, "wbo", 0, temp_name);
TEST_CHECK(unique != 0);
TEST_CHECK(DeleteFileA(temp_name));
TEST_CHECK(CreateDirectoryA(temp_name, NULL));
strncpy(g_fixture_dir, temp_name, sizeof(g_fixture_dir));
g_fixture_dir[sizeof(g_fixture_dir) - 1] = '\0';
TEST_CHECK(SetCurrentDirectoryA(g_fixture_dir));
TEST_CHECK(CreateDirectoryA("dir", NULL));
TEST_CHECK(CreateDirectoryA("dir\\child", NULL));
TEST_CHECK(CreateDirectoryA("dir_extra", NULL));
create_file_with_content("dir\\file.txt", "file.txt\n");
create_file_with_content("dir\\file.bin", "file.bin\n");
create_file_with_content("dir\\data01.txt", "data01\n");
create_file_with_content("dir\\data02.txt", "data02\n");
create_file_with_content("dir\\data10.txt", "data10\n");
create_file_with_content("dir\\child\\nested.txt", "nested\n");
create_file_with_content("dir_extra\\other.txt", "other\n");
}
static void cleanup_fixture(void) {
TEST_CHECK(SetCurrentDirectoryA(g_original_dir));
char path[MAX_PATH];
join_path(path, sizeof(path), g_fixture_dir, "dir\\child\\nested.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\child");
RemoveDirectoryA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\file.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\file.bin");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\data01.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\data02.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir\\data10.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir");
RemoveDirectoryA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir_extra\\other.txt");
DeleteFileA(path);
join_path(path, sizeof(path), g_fixture_dir, "dir_extra");
RemoveDirectoryA(path);
RemoveDirectoryA(g_fixture_dir);
}
static HANDLE find_first_checked(const char *pattern, WIN32_FIND_DATAA *out_data) {
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA(pattern, out_data);
TEST_CHECK_MSG(handle != INVALID_HANDLE_VALUE, "FindFirstFileA failed for %s (err=%lu)", pattern, GetLastError());
return handle;
}
static void test_empty_pattern(void) {
WIN32_FIND_DATAA data;
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA("", &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_PATH_NOT_FOUND, GetLastError());
}
static void test_null_pattern(void) {
WIN32_FIND_DATAA data;
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA(NULL, &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_PATH_NOT_FOUND, GetLastError());
}
static void test_dot_pattern(void) {
WIN32_FIND_DATAA data;
HANDLE handle = find_first_checked(".", &data);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
TEST_CHECK(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
TEST_CHECK_STR_EQ(leaf_name(g_fixture_dir), data.cFileName);
SetLastError(0xDEADBEEF);
TEST_CHECK(!FindNextFileA(handle, &data));
TEST_CHECK_EQ(ERROR_NO_MORE_FILES, GetLastError());
TEST_CHECK(FindClose(handle));
}
static void test_trailing_slash(void) {
WIN32_FIND_DATAA data;
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA("dir\\", &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_FILE_NOT_FOUND, GetLastError());
SetLastError(0xDEADBEEF);
handle = FindFirstFileA("dir/", &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_FILE_NOT_FOUND, GetLastError());
}
static void test_trailing_dot(void) {
WIN32_FIND_DATAA data;
HANDLE handle = find_first_checked("dir\\.", &data);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
TEST_CHECK(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
TEST_CHECK_STR_EQ("dir", data.cFileName);
TEST_CHECK(FindClose(handle));
handle = find_first_checked("dir/.", &data);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
TEST_CHECK(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
TEST_CHECK_STR_EQ("dir", data.cFileName);
TEST_CHECK(FindClose(handle));
}
static void test_direct_file_paths(void) {
WIN32_FIND_DATAA data;
HANDLE handle = find_first_checked("dir\\file.txt", &data);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
TEST_CHECK((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
TEST_CHECK_STR_EQ("file.txt", data.cFileName);
SetLastError(0xDEADBEEF);
TEST_CHECK(!FindNextFileA(handle, &data));
TEST_CHECK_EQ(ERROR_NO_MORE_FILES, GetLastError());
TEST_CHECK(FindClose(handle));
handle = find_first_checked("dir/file.txt", &data);
TEST_CHECK(handle != INVALID_HANDLE_VALUE);
TEST_CHECK((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
TEST_CHECK_STR_EQ("file.txt", data.cFileName);
SetLastError(0xDEADBEEF);
TEST_CHECK(!FindNextFileA(handle, &data));
TEST_CHECK_EQ(ERROR_NO_MORE_FILES, GetLastError());
TEST_CHECK(FindClose(handle));
}
static int compare_strings(const void *a, const void *b) {
const char *sa = (const char *)a;
const char *sb = (const char *)b;
return strcmp(sa, sb);
}
static void collect_matches(const char *pattern, char matches[][MAX_PATH], size_t *out_count) {
*out_count = 0;
WIN32_FIND_DATAA data;
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA(pattern, &data);
if (handle == INVALID_HANDLE_VALUE) {
*out_count = 0;
return;
}
do {
strncpy(matches[*out_count], data.cFileName, MAX_PATH);
matches[*out_count][MAX_PATH - 1] = '\0';
(*out_count)++;
TEST_CHECK(*out_count < 64);
} while (FindNextFileA(handle, &data));
TEST_CHECK_EQ(ERROR_NO_MORE_FILES, GetLastError());
TEST_CHECK(FindClose(handle));
}
static void test_wildcard_star(void) {
char matches[64][MAX_PATH];
size_t count = 0;
collect_matches("dir\\*.txt", matches, &count);
TEST_CHECK(count >= 3);
qsort(matches, count, sizeof(matches[0]), compare_strings);
bool saw_data01 = false;
bool saw_data02 = false;
bool saw_file = false;
for (size_t i = 0; i < count; ++i) {
saw_data01 = saw_data01 || strcmp(matches[i], "data01.txt") == 0;
saw_data02 = saw_data02 || strcmp(matches[i], "data02.txt") == 0;
saw_file = saw_file || strcmp(matches[i], "file.txt") == 0;
}
TEST_CHECK(saw_data01);
TEST_CHECK(saw_data02);
TEST_CHECK(saw_file);
}
static void test_wildcard_question(void) {
char matches[64][MAX_PATH];
size_t count = 0;
collect_matches("dir\\data0?.txt", matches, &count);
TEST_CHECK_EQ(2, count);
qsort(matches, count, sizeof(matches[0]), compare_strings);
TEST_CHECK_STR_EQ("data01.txt", matches[0]);
TEST_CHECK_STR_EQ("data02.txt", matches[1]);
count = 0;
collect_matches("dir\\.\\data1?.txt", matches, &count);
TEST_CHECK_EQ(1, count);
TEST_CHECK_STR_EQ("data10.txt", matches[0]);
count = 0;
collect_matches("dir\\child\\..\\data??.txt", matches, &count);
TEST_CHECK_EQ(3, count);
qsort(matches, count, sizeof(matches[0]), compare_strings);
TEST_CHECK_STR_EQ("data01.txt", matches[0]);
TEST_CHECK_STR_EQ("data02.txt", matches[1]);
TEST_CHECK_STR_EQ("data10.txt", matches[2]);
}
static void test_wildcard_in_directory_segment(void) {
WIN32_FIND_DATAA data;
SetLastError(0xDEADBEEF);
HANDLE handle = FindFirstFileA("dir*\\file.txt", &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_INVALID_NAME, GetLastError());
SetLastError(0xDEADBEEF);
handle = FindFirstFileA("dir*\\child\\nested.txt", &data);
TEST_CHECK(handle == INVALID_HANDLE_VALUE);
TEST_CHECK_EQ(ERROR_INVALID_NAME, GetLastError());
}
static void test_directory_iteration_includes_special_entries(void) {
char matches[64][MAX_PATH];
size_t count = 0;
collect_matches("dir\\*", matches, &count);
TEST_CHECK(count >= 5);
bool saw_dot = false;
bool saw_dotdot = false;
bool saw_child = false;
for (size_t i = 0; i < count; ++i) {
saw_dot = saw_dot || strcmp(matches[i], ".") == 0;
saw_dotdot = saw_dotdot || strcmp(matches[i], "..") == 0;
saw_child = saw_child || strcmp(matches[i], "child") == 0;
}
TEST_CHECK(saw_dot);
TEST_CHECK(saw_dotdot);
TEST_CHECK(saw_child);
}
static void test_findclose_invalid_handle(void) {
SetLastError(0xDEADBEEF);
TEST_CHECK(!FindClose(NULL));
TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError());
}
int main(void) {
setup_fixture();
test_empty_pattern();
test_null_pattern();
test_dot_pattern();
test_trailing_slash();
test_trailing_dot();
test_direct_file_paths();
test_wildcard_star();
test_wildcard_question();
test_wildcard_in_directory_segment();
test_directory_iteration_includes_special_entries();
test_findclose_invalid_handle();
cleanup_fixture();
return EXIT_SUCCESS;
}