366 lines
15 KiB
C
366 lines
15 KiB
C
#include "c_File.h"
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <c_Test.h>
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
static const char* TEST_DIR = "./test_sandbox";
|
||
static const char* TEST_FILE = "./test_sandbox/test_data.txt";
|
||
static const char* NESTED_DIR_ROOT = "./test_sandbox_deep";
|
||
static const char* NESTED_DIR_SUB1 = "./test_sandbox_deep/level1";
|
||
static const char* NESTED_DIR_SUB2 = "./test_sandbox_deep/level1/level2";
|
||
static const char* NESTED_DEEP_FILE = "./test_sandbox_deep/level1/level2/target.txt";
|
||
static const char* TEST_DEL_FILE = "./test_sandbox/delete_target.txt";
|
||
static const char* FILE_SRC = "./test_sandbox/copy_src.txt";
|
||
static const char* FILE_COPY = "./test_sandbox/copy_dest.txt";
|
||
static const char* FILE_MOVE = "./test_sandbox/move_dest.txt";
|
||
|
||
static c_File_t g_file;
|
||
|
||
// 每个文件测试开始前:创建目录确保沙盒环境存在,并重置文件结构体
|
||
void setup_file_env() {
|
||
c_File_MkDirs(TEST_DIR);
|
||
g_file.fp = NULL;
|
||
}
|
||
|
||
// 每个文件测试结束后:强制关闭可能遗留的文件流,并清理生成的临时测试文件
|
||
void teardown_file_env() {
|
||
if (g_file.fp != NULL) {
|
||
c_File_Close(&g_file);
|
||
}
|
||
// 移除沙盒内的文件(如果存在)
|
||
remove(TEST_FILE);
|
||
// 移除沙盒目录(Linux/macOS 下使用 rmdir,为保持跨平台通用这里主要移除文件)
|
||
remove(TEST_DIR);
|
||
}
|
||
|
||
void setup_deep_dir_env() {
|
||
// 1. 建立一个多级深层嵌套的目录
|
||
c_File_MkDirs(NESTED_DIR_SUB2);
|
||
}
|
||
|
||
void teardown_deep_dir_env() {
|
||
// 兜底清理:如果测试挂了,防止残留污染本地磁盘
|
||
// 在这里直接调用它自己完成强制扫尾
|
||
c_File_Rmdirs(NESTED_DIR_ROOT);
|
||
}
|
||
|
||
void setup_delete_env() {
|
||
// 确保测试沙盒目录存在
|
||
c_File_MkDirs("./test_sandbox");
|
||
}
|
||
|
||
void teardown_delete_env() {
|
||
// 扫尾清理,防止测试中断导致文件残留
|
||
remove(TEST_DEL_FILE);
|
||
remove("./test_sandbox");
|
||
}
|
||
|
||
|
||
void setup_move_copy_env() {
|
||
c_File_MkDirs("./test_sandbox");
|
||
}
|
||
|
||
void teardown_move_copy_env() {
|
||
// 强制清理,防止测试中断产生磁盘残留
|
||
remove(FILE_SRC);
|
||
remove(FILE_COPY);
|
||
remove(FILE_MOVE);
|
||
remove("./test_sandbox");
|
||
}
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
|
||
void test_file_write_and_size() {
|
||
// 1. 以只写/新建模式打开文件
|
||
c_err_t err = c_File_Open(&g_file, TEST_FILE, "wb");
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Failed to open file for writing");
|
||
ASSERT_MSG(g_file.fp != NULL, "File pointer should not be NULL after open");
|
||
|
||
// 2. 检查刚创建的文件是否存在
|
||
ASSERT_MSG(c_File_IsExist(TEST_FILE) == C_TRUE, "File should exist on disk");
|
||
|
||
// 3. 写入数据
|
||
const char* content = "Hello TinyTest Framework!";
|
||
c_size_t bytes_to_write = strlen(content);
|
||
c_size_t bytes_written = 0;
|
||
|
||
err = c_File_Write(&g_file, (void*)content, bytes_to_write, &bytes_written);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "File write error");
|
||
ASSERT_INT_EQ_MSG((int)bytes_to_write, (int)bytes_written, "Written size mismatched");
|
||
|
||
// 4. 刷新缓冲区
|
||
err = c_File_Flush(&g_file);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "File flush error");
|
||
|
||
// 5. 校验文件大小是否和写入的字节数严格对齐
|
||
long long f_size = c_File_Size(&g_file);
|
||
ASSERT_INT_EQ_MSG((int)bytes_to_write, (int)f_size, "File size reported incorrectly");
|
||
|
||
// 6. 正常关闭文件
|
||
c_File_Close(&g_file);
|
||
}
|
||
|
||
|
||
// 用例 2:测试文件的读取(Read)以及指针重定位(Seek)
|
||
void test_file_read_and_seek() {
|
||
// 预备工作:先写入一串已知文本用于后续测试
|
||
c_File_Open(&g_file, TEST_FILE, "wb");
|
||
const char* dummy_data = "abcdefghij"; // 10 字节
|
||
c_size_t written = 0;
|
||
c_File_Write(&g_file, (void*)dummy_data, 10, &written);
|
||
c_File_Close(&g_file);
|
||
|
||
// 1. 以只读模式重新打开文件
|
||
c_err_t err = c_File_Open(&g_file, TEST_FILE, "rb");
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Failed to open file for reading");
|
||
|
||
// 2. 测试基础顺序读取(先读取 4 字节,应该读到 "abcd")
|
||
char buffer[16] = {0};
|
||
c_size_t bytes_read = 0;
|
||
err = c_File_Read(&g_file, buffer, 4, &bytes_read);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "File read error");
|
||
ASSERT_INT_EQ_MSG(4, (int)bytes_read, "Should read exactly 4 bytes");
|
||
ASSERT_INT_EQ_MSG(0, memcmp(buffer, "abcd", 4), "Buffer content mismatches on initial read");
|
||
|
||
// 3. 测试文件指针重定位:移到绝对位置索引 5 处(对应字符 'f')
|
||
err = c_File_Seek(&g_file, 5);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "File seek error");
|
||
|
||
// 4. 定位后再次读取 3 字节(应该读到 "fgh")
|
||
memset(buffer, 0, sizeof(buffer));
|
||
err = c_File_Read(&g_file, buffer, 3, &bytes_read);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "File read error after seeking");
|
||
ASSERT_INT_EQ_MSG(3, (int)bytes_read, "Should read 3 bytes after seek");
|
||
ASSERT_INT_EQ_MSG(0, memcmp(buffer, "fgh", 3), "Buffer content mismatches after seek");
|
||
|
||
c_File_Close(&g_file);
|
||
}
|
||
|
||
|
||
// 用例 3:测试按行读取(Readline)文本的边界与断行识别
|
||
void test_file_read_line() {
|
||
// 预备工作:写入包含多行换行符的文本
|
||
c_File_Open(&g_file, TEST_FILE, "wb");
|
||
const char* lines = "Line1\nLine2\r\nLine3";
|
||
c_size_t written = 0;
|
||
c_File_Write(&g_file, (void*)lines, strlen(lines), &written);
|
||
c_File_Close(&g_file);
|
||
|
||
// 1. 打开文件开始按行验证
|
||
c_File_Open(&g_file, TEST_FILE, "rb");
|
||
|
||
char line_buf[32];
|
||
c_size_t read_len = 0;
|
||
|
||
// 读取第一行(预期为 "Line1\n" 或处理掉换行符的 "Line1" 视你内部实现而定,通常 fgets 保留换行)
|
||
c_err_t err = c_File_Readline(&g_file, line_buf, sizeof(line_buf), &read_len);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Readline 1 failed");
|
||
ASSERT_MSG(strstr(line_buf, "Line1") != NULL, "Line 1 content error");
|
||
|
||
// 读取第二行
|
||
err = c_File_Readline(&g_file, line_buf, sizeof(line_buf), &read_len);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Readline 2 failed");
|
||
ASSERT_MSG(strstr(line_buf, "Line2") != NULL, "Line 2 content error");
|
||
|
||
c_File_Close(&g_file);
|
||
}
|
||
|
||
|
||
// 用例 4:测试文件不存在、无效路径时的防御性报错
|
||
void test_file_invalid_operations() {
|
||
// 1. 检查一个绝对不存在的文件
|
||
c_bool_t exist = c_File_IsExist("./this_file_does_not_exist_12345.xyz");
|
||
ASSERT_INT_EQ_MSG(C_FALSE, exist, "IsExist should return FALSE for phantom files");
|
||
|
||
// 2. 尝试打开一个不存在的文件用于只读,应当安全返回错误码,且内部指针保持为 NULL
|
||
c_File_t bad_file = {NULL};
|
||
c_err_t err = c_File_Open(&bad_file, "./phantom_file.txt", "rb");
|
||
ASSERT_MSG(err != C_ERR_OK, "Opening non-existent file for read must fail");
|
||
ASSERT_MSG(bad_file.fp == NULL, "Failed open must keep fp as NULL");
|
||
}
|
||
|
||
void test_file_rmdirs_force_delete_nested() {
|
||
// 1. 验证前置多级目录环境已经被 SetUp 成功拉起
|
||
ASSERT_MSG(c_File_IsExist(NESTED_DIR_SUB2) == C_TRUE, "Setup failed to prepare nested dir");
|
||
|
||
// 2. 在最深层目录 level2 里写入一个真实的文本文件,夯实其“非空”属性
|
||
c_File_t file;
|
||
c_err_t err = c_File_Open(&file, NESTED_DEEP_FILE, "wb");
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Failed to create deep file inside test sandbox");
|
||
|
||
const char* dummy = "deep data";
|
||
c_size_t written = 0;
|
||
c_File_Write(&file, (void*)dummy, strlen(dummy), &written);
|
||
c_File_Close(&file);
|
||
|
||
// 再次确认文件已在磁盘落地
|
||
ASSERT_MSG(c_File_IsExist(NESTED_DEEP_FILE) == C_TRUE, "Deep text file should exist before wipe");
|
||
|
||
// 3. 一剑封喉:直接调用 Rmdirs 强删最外层的根目录 NESTED_DIR_ROOT
|
||
c_err_t rmdir_status = c_File_Rmdirs(NESTED_DIR_ROOT);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, rmdir_status, "c_File_Rmdirs failed to clear nested architecture");
|
||
|
||
// 4. 断言验证:整棵目录树必须从盘面上被完全抹除
|
||
ASSERT_MSG(c_File_IsExist(NESTED_DEEP_FILE) == C_FALSE, "Deep file should have been blasted");
|
||
ASSERT_MSG(c_File_IsExist(NESTED_DIR_SUB2) == C_FALSE, "Level 2 directory should be wiped");
|
||
ASSERT_MSG(c_File_IsExist(NESTED_DIR_ROOT) == C_FALSE, "Root sandbox directory must be cleared");
|
||
}
|
||
|
||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
/* */
|
||
|
||
// 用例 1:测试常规文件的正常创建与成功删除
|
||
void test_file_delete_success() {
|
||
// 1. 先创建一个真实的文件
|
||
c_File_t file;
|
||
c_err_t err = c_File_Open(&file, TEST_DEL_FILE, "wb");
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Failed to create delete-target file");
|
||
|
||
const char* dummy = "delete me";
|
||
c_size_t written = 0;
|
||
c_File_Write(&file, (void*)dummy, strlen(dummy), &written);
|
||
c_File_Close(&file);
|
||
|
||
// 2. 确认文件确实落地并在磁盘中存在
|
||
ASSERT_MSG(c_File_IsExist(TEST_DEL_FILE) == C_TRUE, "Target file must exist before deletion");
|
||
|
||
// 3. 执行删除操作
|
||
c_err_t del_err = c_File_Delete(TEST_DEL_FILE);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, del_err, "c_File_Delete failed on a standard closed file");
|
||
|
||
// 4. 再次验证文件是否已经从文件系统中消失
|
||
ASSERT_MSG(c_File_IsExist(TEST_DEL_FILE) == C_FALSE, "Target file should be gone after c_File_Delete");
|
||
}
|
||
|
||
// 用例 2:测试删除一个完全不存在的文件时的防御表现
|
||
void test_file_delete_non_existent() {
|
||
const char* phantom_file = "./test_sandbox/ghost_file_999.xyz";
|
||
|
||
// 确保该路径当前确实不存在
|
||
ASSERT_MSG(c_File_IsExist(phantom_file) == C_FALSE, "Phantom file should not exist");
|
||
|
||
// 尝试执行删除
|
||
c_err_t err = c_File_Delete(phantom_file);
|
||
|
||
// 应当安全返回非 OK 的错误状态,且程序绝不能发生崩溃
|
||
ASSERT_MSG(err != C_ERR_OK, "c_File_Delete must report an error when trying to delete a non-existent file");
|
||
}
|
||
|
||
// 用例 3:测试删除一个正处于“打开/占用状态”的文件(进阶边界测试)
|
||
void test_file_delete_while_open() {
|
||
// 1. 创建并保持打开该文件,故意不执行 Close
|
||
c_File_t file;
|
||
c_err_t err = c_File_Open(&file, TEST_DEL_FILE, "wb");
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Failed to open file for lock-test");
|
||
|
||
// 2. 尝试在文件流未关闭的情况下,强行调用接口删除它
|
||
c_err_t del_err = c_File_Delete(TEST_DEL_FILE);
|
||
|
||
/*
|
||
* 注意:这里的断言取决于你对框架跨平台容忍度的设计。
|
||
* - Windows 底层会因为文件处于 Share Violation 锁死状态而直接拒绝删除,返回错误码(del_err != C_ERR_OK)。
|
||
* - Linux / POSIX 则允许执行 unlink 删除,表现为返回 C_ERR_OK,但文件直到 Close 后才会真正释放空间。
|
||
* 为了让你的测试能在多平台安全兼容,我们主要确保程序不会挂死崩溃,并打印当前表现。
|
||
*/
|
||
printf(" [INFO] c_File_Delete while open returned: %d (Platform dependent behaviour)\n", del_err);
|
||
|
||
// 无论刚才删除成功与否,为了测试框架的安全,我们都要显式地进行资源关闭和文件清理
|
||
c_File_Close(&file);
|
||
remove(TEST_DEL_FILE);
|
||
}
|
||
|
||
// 用例 1:验证文件流式复制 c_File_Copy
|
||
void test_file_copy_integrity() {
|
||
// 1. 准备源文件并写入特定文本
|
||
c_File_t src_file;
|
||
c_File_Open(&src_file, FILE_SRC, "wb");
|
||
const char* pattern = "Copy & Move Structural Verification Data.";
|
||
c_size_t written = 0;
|
||
c_File_Write(&src_file, (void*)pattern, strlen(pattern), &written);
|
||
c_File_Close(&src_file);
|
||
|
||
// 2. 调用复制函数
|
||
c_err_t err = c_File_Copy(FILE_SRC, FILE_COPY);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "c_File_Copy execution failed");
|
||
|
||
// 3. 断言验证:目标文件必须存在,且大小必须完全一致
|
||
ASSERT_MSG(c_File_IsExist(FILE_COPY) == C_TRUE, "Copied target file does not exist");
|
||
|
||
c_File_t dest_file;
|
||
c_File_Open(&dest_file, FILE_COPY, "rb");
|
||
long long copy_size = c_File_Size(&dest_file);
|
||
|
||
char read_buf[128] = {0};
|
||
c_size_t read_bytes = 0;
|
||
c_File_Read(&dest_file, read_buf, sizeof(read_buf) - 1, &read_bytes);
|
||
c_File_Close(&dest_file);
|
||
|
||
ASSERT_INT_EQ_MSG((int)strlen(pattern), (int)copy_size, "Copied file size mismatches");
|
||
ASSERT_INT_EQ_MSG(0, strcmp(pattern, read_buf), "Copied data content corruption detected");
|
||
}
|
||
|
||
// 用例 2:验证文件移动与重命名 c_File_Move
|
||
void test_file_move_behavior() {
|
||
// 1. 再次在旧路径上创建一个基准文件
|
||
c_File_t src_file;
|
||
c_File_Open(&src_file, FILE_SRC, "wb");
|
||
const char* payload = "MovePayload";
|
||
c_size_t written = 0;
|
||
c_File_Write(&src_file, (void*)payload, strlen(payload), &written);
|
||
c_File_Close(&src_file);
|
||
|
||
// 2. 调用移动函数
|
||
c_err_t err = c_File_Move(FILE_SRC, FILE_MOVE);
|
||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "c_File_Move execution failed");
|
||
|
||
// 3. 断言验证:【关键点】旧文件必须在文件系统中消失,新路径下必须出现该文件
|
||
ASSERT_MSG(c_File_IsExist(FILE_SRC) == C_FALSE, "Source file should be gone after move");
|
||
ASSERT_MSG(c_File_IsExist(FILE_MOVE) == C_TRUE, "Moved destination file should exist");
|
||
|
||
// 4. 读取内容验证原子性与准确性
|
||
c_File_t moved_file;
|
||
c_File_Open(&moved_file, FILE_MOVE, "rb");
|
||
char read_buf[32] = {0};
|
||
c_size_t read_bytes = 0;
|
||
c_File_Read(&moved_file, read_buf, sizeof(read_buf) - 1, &read_bytes);
|
||
c_File_Close(&moved_file);
|
||
|
||
ASSERT_INT_EQ_MSG(0, strcmp(payload, read_buf), "Moved file content mismatches");
|
||
}
|
||
|
||
// 用例 3:验证对非正常状态路径(不存在的源文件)调用 Copy 的防御机制
|
||
void test_file_copy_non_existent() {
|
||
c_err_t err = c_File_Copy("./test_sandbox/non_exist_source_xyz.dat", FILE_COPY);
|
||
// 应该优雅报错,不能返回 C_ERR_OK
|
||
ASSERT_MSG(err != C_ERR_OK, "Copying a non-existent file must return error status");
|
||
}
|
||
|
||
int main(int argc, char** argv){
|
||
|
||
TEST_START(Starting Unit Tests);
|
||
|
||
RUN_TEST_FIXTURE(test_file_write_and_size, setup_file_env, teardown_file_env);
|
||
RUN_TEST_FIXTURE(test_file_read_and_seek, setup_file_env, teardown_file_env);
|
||
RUN_TEST_FIXTURE(test_file_read_line, setup_file_env, teardown_file_env);
|
||
RUN_TEST_FIXTURE(test_file_invalid_operations, setup_file_env, teardown_file_env);
|
||
RUN_TEST_FIXTURE(test_file_rmdirs_force_delete_nested, setup_deep_dir_env, teardown_deep_dir_env);
|
||
RUN_TEST_FIXTURE(test_file_delete_success, setup_delete_env, teardown_delete_env);
|
||
RUN_TEST_FIXTURE(test_file_delete_non_existent, setup_delete_env, teardown_delete_env);
|
||
RUN_TEST_FIXTURE(test_file_delete_while_open, setup_delete_env, teardown_delete_env);
|
||
RUN_TEST_FIXTURE(test_file_copy_integrity, setup_move_copy_env, teardown_move_copy_env);
|
||
RUN_TEST_FIXTURE(test_file_move_behavior, setup_move_copy_env, teardown_move_copy_env);
|
||
RUN_TEST_FIXTURE(test_file_copy_non_existent, setup_move_copy_env, teardown_move_copy_env);
|
||
|
||
// 打印最终统计报告
|
||
TEST_REPORT();
|
||
|
||
RETURN_TEST_STATUS;
|
||
|
||
return 0;
|
||
}
|