测试用例
This commit is contained in:
+155
-122
@@ -2,144 +2,177 @@
|
||||
#define INCLUDED_C_TEST_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <time.h>
|
||||
#include "c_Compiler.h"
|
||||
|
||||
/* ==============================================================================
|
||||
* 🎯 1. 测试上下文状态追踪
|
||||
* ============================================================================== */
|
||||
static int c_test_suite_failed = 0; /* 当前套件是否有失败的断言 */
|
||||
static int c_test_case_failed = 0; /* 当前正在运行的测试用例是否有失败 */
|
||||
static int c_test_total_assertions = 0; /* 总断言数 */
|
||||
static int c_test_passed_assertions = 0; /* 成功的断言数 */
|
||||
static int c_test_cases_run = 0; /* 运行的用例数 */
|
||||
static int c_test_cases_passed = 0; /* 成功的用例数 */
|
||||
static double c_test_suite_total_ms = 0.0; /* 整个套件的总耗时 */
|
||||
// ==========================================
|
||||
// ANSI 终端颜色宏定义
|
||||
// ==========================================
|
||||
#define COLOR_RESET "\033[0m"
|
||||
#define COLOR_GREEN "\033[32m"
|
||||
#define COLOR_RED "\033[31m"
|
||||
#define COLOR_YELLOW "\033[33m"
|
||||
#define COLOR_BLUE "\033[34m"
|
||||
#define COLOR_CYAN "\033[36m"
|
||||
#define COLOR_BOLD "\033[1m"
|
||||
|
||||
/* ==============================================================================
|
||||
* 🎯 2. 核心断言宏矩阵(严格以 C_ASSERT_ 开头)
|
||||
* ============================================================================== */
|
||||
// ==========================================
|
||||
// 核心测试框架结构
|
||||
// ==========================================
|
||||
typedef struct {
|
||||
int passed_count;
|
||||
int failed_count;
|
||||
double total_time_ms;
|
||||
} TestRegistry;
|
||||
|
||||
/* 统一的断言失败内部打印函数 */
|
||||
C_STATIC_FORCE_INLINE
|
||||
void c_test_print_fail(const char* file, int line, const char* expr, const char* msg) {
|
||||
c_test_suite_failed = 1;
|
||||
c_test_case_failed = 1;
|
||||
printf(" [FAIL] %s:%d -> 断言失败: (%s) ", file, line, expr);
|
||||
if (msg && strlen(msg) > 0) {
|
||||
printf("| 说明: %s", msg);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 基础布尔断言 */
|
||||
#define C_ASSERT(expr, msg) do { \
|
||||
c_test_total_assertions++; \
|
||||
if (C_LIKELY(expr)) { \
|
||||
c_test_passed_assertions++; \
|
||||
} else { \
|
||||
c_test_print_fail(__FILE__, __LINE__, #expr, msg); \
|
||||
} \
|
||||
} while(0)
|
||||
static TestRegistry g_test_registry = {0, 0, 0.0};
|
||||
|
||||
/* 整型相等断言 */
|
||||
#define C_ASSERT_INT_EQ(actual, expected, msg) do { \
|
||||
c_test_total_assertions++; \
|
||||
long long act = (long long)(actual); \
|
||||
long long exp = (long long)(expected); \
|
||||
if (C_LIKELY(act == exp)) { \
|
||||
c_test_passed_assertions++; \
|
||||
} else { \
|
||||
char buf[256]; \
|
||||
snprintf(buf, sizeof(buf), "期望值: %lld, 实际值: %lld | %s", exp, act, msg); \
|
||||
c_test_print_fail(__FILE__, __LINE__, #actual " == " #expected, buf); \
|
||||
} \
|
||||
} while(0)
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/* 字符串相等断言 */
|
||||
#define C_ASSERT_STR_EQ(actual, expected, msg) do { \
|
||||
c_test_total_assertions++; \
|
||||
const char* act = (const char*)(actual); \
|
||||
const char* exp = (const char*)(expected); \
|
||||
if (C_LIKELY(act && exp && strcmp(act, exp) == 0)) { \
|
||||
c_test_passed_assertions++; \
|
||||
} else { \
|
||||
char buf[256]; \
|
||||
snprintf(buf, sizeof(buf), "期望: \"%s\", 实际: \"%s\" | %s", exp ? exp : "NULL", act ? act : "NULL", msg); \
|
||||
c_test_print_fail(__FILE__, __LINE__, "strcmp(" #actual ", " #expected ") == 0", buf); \
|
||||
} \
|
||||
} while(0)
|
||||
#define ASSERT_MSG(condition, message) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Assertion failed: (%s). Message: %s" COLOR_RESET "\n", __FILE__, __LINE__, #condition, message); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* 浮点数安全相等断言(内置之前讨论的 IEEE 754 Epsilon 比较) */
|
||||
#define C_ASSERT_DOUBLE_EQ(actual, expected, msg) do { \
|
||||
c_test_total_assertions++; \
|
||||
double act = (double)(actual); \
|
||||
double exp = (double)(expected); \
|
||||
int is_eq = 0; \
|
||||
if (isnan(act) && isnan(exp) || (fabs(act - exp) < DBL_EPSILON) ) { is_eq = 1; } \
|
||||
if (C_LIKELY(is_eq)) { \
|
||||
c_test_passed_assertions++; \
|
||||
} else { \
|
||||
char buf[256]; \
|
||||
snprintf(buf, sizeof(buf), "期望: %.32f, 实际: %.32f (误差 > DBL_EPSILON[%.32f]) | %s", exp, act, DBL_EPSILON, msg); \
|
||||
c_test_print_fail(__FILE__, __LINE__, "fabs(" #actual " - " #expected ") < DBL_EPSILON", buf); \
|
||||
} \
|
||||
} while(0)
|
||||
#define ASSERT_INT_EQ_MSG(expected, actual, message) \
|
||||
do { \
|
||||
if ((expected) != (actual)) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Expected %d, but got %d. Message: %s" COLOR_RESET "\n", __FILE__, __LINE__, (int)(expected), (int)(actual), message); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* 🎯 3. 测试套件生命周期控制宏
|
||||
* ============================================================================== */
|
||||
#ifndef TEST_EPSILON
|
||||
#define TEST_EPSILON 1e-6
|
||||
#endif
|
||||
|
||||
/* 初始化一个测试套件文件 */
|
||||
#define C_TEST_SUITE_BEGIN(name) \
|
||||
printf("==================================================\n"); \
|
||||
printf("🧪 运行测试套件: %s [%s环境]\n", #name, C_CURRENT_OS_NAME); \
|
||||
printf("==================================================\n");
|
||||
#define ASSERT_DOUBLE_EQ_MSG(expected, actual, message) \
|
||||
do { \
|
||||
double __exp = (double)(expected); \
|
||||
double __act = (double)(actual); \
|
||||
/* 计算两个浮点数差值的绝对值,并与允许的误差进行比较 */ \
|
||||
if (fabs(__exp - __act) > TEST_EPSILON) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Expected %.6f, but got %.6f. Message: %s" COLOR_RESET "\n", \
|
||||
__FILE__, __LINE__, __exp, __act, (message) ? (message) : "No message"); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* 结束测试套件并返回标准状态码给 CMake CTest */
|
||||
#define C_TEST_SUITE_END() \
|
||||
printf("\n--------------------------------------------------------------------\n"); \
|
||||
printf("📊 测试结果统计:\n"); \
|
||||
printf(" 测试用例数: %d 运行, %d 通过, %d 失败\n", \
|
||||
c_test_cases_run, c_test_cases_passed, c_test_cases_run - c_test_cases_passed); \
|
||||
printf(" 底层断言数: %d 总计, %d 成功, %d 失败\n", \
|
||||
c_test_total_assertions, c_test_passed_assertions, c_test_total_assertions - c_test_passed_assertions); \
|
||||
printf(" 套件总耗时: %.3f ms\n", c_test_suite_total_ms); \
|
||||
if (c_test_suite_failed) { \
|
||||
printf("🚨 结论 : [ FAILURE ] 有测试未通过!\n"); \
|
||||
printf("====================================================================\n"); \
|
||||
return 1; \
|
||||
} \
|
||||
printf("🎉 结论 : [ SUCCESS ] 全套单元测试完美通过!\n"); \
|
||||
printf("====================================================================\n"); \
|
||||
return 0;
|
||||
|
||||
/* 声明一个测试用例块 */
|
||||
#define C_TEST_CASE(name) static void name(void)
|
||||
|
||||
#define C_RUN_TEST_CASE(name) do { \
|
||||
c_test_cases_run++; \
|
||||
c_test_case_failed = 0; \
|
||||
printf(" ▶ 运行测试用例: %-30s ... ", #name); \
|
||||
fflush(stdout); \
|
||||
struct timespec c_start_time, c_end_time; \
|
||||
timespec_get(&c_start_time, TIME_UTC); \
|
||||
name(); \
|
||||
timespec_get(&c_end_time, TIME_UTC); \
|
||||
double c_elapsed_ms = (double)(c_end_time.tv_sec - c_start_time.tv_sec) * 1000.0 + \
|
||||
(double)(c_end_time.tv_nsec - c_start_time.tv_nsec) / 1000000.0; \
|
||||
c_test_suite_total_ms += c_elapsed_ms; \
|
||||
if (c_test_case_failed) { \
|
||||
printf("[ ❌ 失败 ] (耗时: %8.3f ms)\n", c_elapsed_ms); \
|
||||
} else { \
|
||||
c_test_cases_passed++; \
|
||||
printf("[ ✨ 通过 ] (耗时: %8.3f ms)\n", c_elapsed_ms); \
|
||||
} \
|
||||
} while(0)
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
// 基础断言宏
|
||||
#define ASSERT_TRUE(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Assertion failed: (%s)" COLOR_RESET "\n", __FILE__, __LINE__, #condition); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_INT_EQ(expected, actual) \
|
||||
do { \
|
||||
if ((expected) != (actual)) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Expected %d, but got %d" COLOR_RESET "\n", __FILE__, __LINE__, (int)(expected), (int)(actual)); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_LL_EQ(expected, actual) \
|
||||
do { \
|
||||
if ((expected) != (actual)) { \
|
||||
printf(" " COLOR_RED "[FAIL] %s:%d: Expected %"PRId64", but got %"PRId64 COLOR_RESET "\n", __FILE__, __LINE__, (uint64_t)(expected), (uint64_t)(actual)); \
|
||||
g_test_registry.failed_count++; \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_PTR_NOT_NULL(ptr) ASSERT_TRUE(ptr!=NULL)
|
||||
|
||||
// ==========================================
|
||||
// 核心运行宏(支持自定义配置环境)
|
||||
// ==========================================
|
||||
|
||||
// 核心通用运行宏(内部使用)
|
||||
#define _RUN_TEST_CORE(test_func, setup, teardown) \
|
||||
do { \
|
||||
printf(COLOR_BLUE "[RUN ]" COLOR_RESET " %s\n", #test_func); \
|
||||
\
|
||||
/* 1. 执行专属启动函数 */ \
|
||||
void (*st)(void) = (setup); \
|
||||
if (st) st(); \
|
||||
\
|
||||
int initial_failed = g_test_registry.failed_count; \
|
||||
clock_t start_time = clock(); \
|
||||
\
|
||||
/* 2. 执行测试函数 */ \
|
||||
test_func(); \
|
||||
\
|
||||
clock_t end_time = clock(); \
|
||||
double elapsed_ms = ((double)(end_time - start_time) / CLOCKS_PER_SEC) * 1000.0; \
|
||||
g_test_registry.total_time_ms += elapsed_ms; \
|
||||
\
|
||||
/* 3. 执行专属关闭函数 */ \
|
||||
void (*td)(void) = (teardown); \
|
||||
if (td) td(); \
|
||||
\
|
||||
/* 4. 带颜色状态输出 */ \
|
||||
if (g_test_registry.failed_count == initial_failed) { \
|
||||
printf(COLOR_GREEN "[ OK]" COLOR_RESET " %s (%.2f ms)\n", #test_func, elapsed_ms); \
|
||||
g_test_registry.passed_count++; \
|
||||
} else { \
|
||||
printf(COLOR_RED "[FAIL]" COLOR_RESET " %s (%.2f ms)\n", #test_func, elapsed_ms); \
|
||||
} \
|
||||
printf("-------------------------------------------\n"); \
|
||||
} while(0)
|
||||
|
||||
// 宏 1:支持绑定专属环境的运行宏
|
||||
#define RUN_TEST_FIXTURE(test_func, setup, teardown) _RUN_TEST_CORE(test_func, setup, teardown)
|
||||
|
||||
// 宏 2:普通运行宏(无需任何启动/关闭环境)
|
||||
#define RUN_TEST(test_func) _RUN_TEST_CORE(test_func, NULL, NULL)
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
#define TEST_START(Name) do{ \
|
||||
printf(COLOR_BOLD "===========================================\n"); \
|
||||
printf(" %s \n", #Name);\
|
||||
printf("===========================================\n" COLOR_RESET); }while(0)
|
||||
|
||||
#define TEST_REPORT()\
|
||||
do{ \
|
||||
printf(COLOR_BOLD "===========================================\n"); \
|
||||
printf("TEST SUMMARY:\n" COLOR_RESET); \
|
||||
printf(" Total Executed : %d\n", g_test_registry.passed_count + g_test_registry.failed_count); \
|
||||
printf(COLOR_GREEN " Passed : %d\n" COLOR_RESET, g_test_registry.passed_count); \
|
||||
printf(COLOR_RED " Failed : %d\n" COLOR_RESET, g_test_registry.failed_count); \
|
||||
printf(" Total Time : %.2f ms\n", g_test_registry.total_time_ms); \
|
||||
printf(COLOR_BOLD "===========================================\n" COLOR_RESET);\
|
||||
}while (0)
|
||||
|
||||
#define RETURN_TEST_STATUS return (g_test_registry.failed_count > 0 ? 1 : 0)
|
||||
|
||||
#define TEST_CASE(name) static void name(void)
|
||||
|
||||
|
||||
#endif /*INCLUDED_C_TEST_H*/
|
||||
|
||||
+47
-24
@@ -4,36 +4,59 @@
|
||||
// Math/matrix.t.c
|
||||
#include "c_Test.h"
|
||||
|
||||
void heavy_calculation(void) {
|
||||
double sum = 0.0;
|
||||
for (int i = 0; i < 5000000; ++i) {
|
||||
sum += sin((double)i) * cos((double)i);
|
||||
}
|
||||
// 随便加一个没意义的断言防止被编译器完全优化掉
|
||||
C_ASSERT(sum != 12345.6, "密集数学计算检验");
|
||||
// ==========================================
|
||||
// 模拟业务环境与测试用例
|
||||
// ==========================================
|
||||
|
||||
int* file_mock_data = NULL;
|
||||
|
||||
// 环境 A 的启动与关闭(针对内存/文件操作)
|
||||
static void setup() {
|
||||
file_mock_data = (int*)malloc(sizeof(int) * 3);
|
||||
file_mock_data[0] = 10; file_mock_data[1] = 20; file_mock_data[2] = 30;
|
||||
printf(" " COLOR_CYAN "[INFO] Setup: Memory initialized." COLOR_RESET "\n");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// 🛠️ 步骤 A: 编写独立的测试用例函数 (C_TEST_CASE)
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
C_TEST_CASE(test_quick_operations) {
|
||||
int a = 1, b = 1;
|
||||
C_ASSERT_INT_EQ(a, b, "极速操作验证");
|
||||
static void teardown() {
|
||||
free(file_mock_data);
|
||||
file_mock_data = NULL;
|
||||
printf(" " COLOR_CYAN "[INFO] Teardown: Memory released." COLOR_RESET "\n");
|
||||
}
|
||||
|
||||
C_TEST_CASE(test_heavy_workload) {
|
||||
heavy_calculation();
|
||||
// --- 具体测试用例 ---
|
||||
|
||||
// 用例 1:需要内存环境
|
||||
static void test_with_memory() {
|
||||
ASSERT_INT_EQ(20, file_mock_data[1]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// 🚀 步骤 B: 在主执行块中注册并依次运行它们
|
||||
// ------------------------------------------------------------------------------
|
||||
int main(int argc, char** argv) {
|
||||
C_TEST_SUITE_BEGIN(MathMatrixFunctionSuite)
|
||||
// 用例 2:故意制造失败,检查内存是否能安全释放,且颜色是否正确
|
||||
static void test_with_memory_failure() {
|
||||
ASSERT_INT_EQ(99, file_mock_data[0]); // 这里会失败并 return 退出
|
||||
}
|
||||
|
||||
C_RUN_TEST_CASE(test_quick_operations);
|
||||
C_RUN_TEST_CASE(test_heavy_workload);
|
||||
// 用例 3:纯数学计算,完全不需要任何专属启动和关闭环境
|
||||
static void test_pure_math() {
|
||||
int result = 1 + 1;
|
||||
ASSERT_INT_EQ(2, result);
|
||||
}
|
||||
|
||||
C_TEST_SUITE_END()
|
||||
|
||||
// ==========================================
|
||||
// 主程序入口
|
||||
// ==========================================
|
||||
int main() {
|
||||
TEST_START(Starting Unit Tests);
|
||||
|
||||
// 运行需要内存环境的用例
|
||||
RUN_TEST_FIXTURE(test_with_memory, setup, teardown);
|
||||
RUN_TEST_FIXTURE(test_with_memory_failure, setup, teardown);
|
||||
|
||||
// 运行普通无环境要求的用例
|
||||
RUN_TEST(test_pure_math);
|
||||
|
||||
// 打印最终统计报告
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
|
||||
+8
-8
@@ -101,16 +101,16 @@ typedef int c_err_t;
|
||||
|
||||
#define C_ERR_OK 0
|
||||
#define C_ERR_FAIL (-1)
|
||||
#define C_ERR_NOTFOUND (-2)
|
||||
#define C_ERR_NOMEM (-3)
|
||||
#define C_ERR_NOTIMPLEMENTED (-4)
|
||||
#define C_ERR_PARAM (-5)
|
||||
#define C_ERR_OUTOFBOUND (-6)
|
||||
#define C_ERR_EMPTY (-7)
|
||||
#define C_ERR_FULL (-8)
|
||||
#define C_ERR_ALREADY_EXISTS (-9)
|
||||
#define C_ERR_NOMEM (-2)
|
||||
#define C_ERR_NOTIMPLEMENTED (-3)
|
||||
#define C_ERR_PARAM (-4)
|
||||
#define C_ERR_EMPTY (-5)
|
||||
#define C_ERR_FULL (-6)
|
||||
#define C_ERR_ALREADY_EXISTS (-7)
|
||||
|
||||
#define C_SUCCESS C_ERR_OK
|
||||
#define C_ERR_NOTFOUND C_ERR_FAIL
|
||||
#define C_ERR_OUTOFBOUND C_ERR_FAIL
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
+45
-49
@@ -8,90 +8,90 @@
|
||||
/* TEST CASES */
|
||||
/* ========================================================================== */
|
||||
|
||||
C_TEST_FRAME_INIT();
|
||||
|
||||
// 1. Stack Initialization and Destruction Loop
|
||||
static
|
||||
void test_array_stack_init_destroy(void) {
|
||||
c_Array_t arr;
|
||||
c_err_t err = c_Array_Init(&arr, 5, sizeof(int));
|
||||
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, err);
|
||||
C_ASSERT_EQ_INT(5, c_Array_Length(&arr));
|
||||
C_ASSERT_EQ_INT(sizeof(int), c_Array_Size(&arr));
|
||||
C_ASSERT_PTR_NOT_NULL(arr.array);
|
||||
ASSERT_LL_EQ(C_SUCCESS, err);
|
||||
ASSERT_LL_EQ(5, c_Array_Length(&arr));
|
||||
ASSERT_LL_EQ(sizeof(int), c_Array_Size(&arr));
|
||||
ASSERT_PTR_NOT_NULL(arr.array);
|
||||
|
||||
c_Array_Destroy(&arr);
|
||||
}
|
||||
|
||||
// 2. Heap Dynamic Allocation Lifecycles
|
||||
void test_array_heap_new_delete(void) {
|
||||
static void test_array_heap_new_delete(void) {
|
||||
c_Array_t* arr = c_Array_New(10, sizeof(double));
|
||||
|
||||
C_ASSERT_PTR_NOT_NULL(arr);
|
||||
C_ASSERT_EQ_INT(10, c_Array_Length(arr));
|
||||
C_ASSERT_EQ_INT(sizeof(double), c_Array_Size(arr));
|
||||
ASSERT_PTR_NOT_NULL(arr);
|
||||
ASSERT_LL_EQ(10, c_Array_Length(arr));
|
||||
ASSERT_LL_EQ(sizeof(double), c_Array_Size(arr));
|
||||
|
||||
c_Array_Delete(&arr);
|
||||
C_ASSERT_PTR_NULL(arr); // Ensure pointer is zeroed out by reference parameter modification
|
||||
ASSERT_TRUE(arr==NULL); // Ensure pointer is zeroed out by reference parameter modification
|
||||
}
|
||||
|
||||
// 3. Put and Get Data Element Scenarios
|
||||
void test_array_put_and_get(void) {
|
||||
static void test_array_put_and_get(void) {
|
||||
c_Array_t* arr = c_Array_New(3, sizeof(int));
|
||||
C_ASSERT_PTR_NOT_NULL(arr);
|
||||
C_ASSERT_EQ_INT(3, arr->length);
|
||||
ASSERT_PTR_NOT_NULL(arr);
|
||||
ASSERT_INT_EQ(3, arr->length);
|
||||
|
||||
int val1 = 100, val2 = 200, val3 = 300;
|
||||
|
||||
// Put elements inside length limits
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 0, &val1));
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 1, &val2));
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 2, &val3));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Put(arr, 0, &val1));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Put(arr, 1, &val2));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Put(arr, 2, &val3));
|
||||
|
||||
// Out of bounds checks
|
||||
int val_bad = 999;
|
||||
C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Put(arr, 3, &val_bad));
|
||||
ASSERT_INT_EQ(C_ERR_PARAM, c_Array_Put(arr, 3, &val_bad));
|
||||
|
||||
// Get verification
|
||||
void* fetch_ptr = NULL;
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Get(arr, 1, &fetch_ptr));
|
||||
C_ASSERT_PTR_NOT_NULL(fetch_ptr);
|
||||
C_ASSERT_EQ_INT(200, *(int*)fetch_ptr);
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Get(arr, 1, &fetch_ptr));
|
||||
ASSERT_PTR_NOT_NULL(fetch_ptr);
|
||||
ASSERT_INT_EQ(200, *(int*)fetch_ptr);
|
||||
|
||||
// Out of bounds get verification
|
||||
C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Get(arr, 5, &fetch_ptr));
|
||||
ASSERT_INT_EQ(C_ERR_PARAM, c_Array_Get(arr, 5, &fetch_ptr));
|
||||
|
||||
c_Array_Delete(&arr);
|
||||
}
|
||||
|
||||
// 4. Memory Resizing Limits Verification
|
||||
void test_array_resize(void) {
|
||||
static void test_array_resize(void) {
|
||||
c_Array_t* arr = c_Array_New(2, sizeof(int));
|
||||
int val0 = 42, val1 = 84;
|
||||
c_Array_Put(arr, 0, &val0);
|
||||
c_Array_Put(arr, 1, &val1);
|
||||
|
||||
// Resize up to 4 elements
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Resize(arr, 4));
|
||||
C_ASSERT_EQ_INT(4, c_Array_Length(arr));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Resize(arr, 4));
|
||||
ASSERT_INT_EQ(4, c_Array_Length(arr));
|
||||
|
||||
// Verify older elements remain structurally untouched
|
||||
void* res_ptr = NULL;
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Get(arr, 1, &res_ptr));
|
||||
C_ASSERT_EQ_INT(84, *(int*)res_ptr);
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Get(arr, 1, &res_ptr));
|
||||
ASSERT_INT_EQ(84, *(int*)res_ptr);
|
||||
|
||||
// Resize down to 1 element
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Resize(arr, 1));
|
||||
C_ASSERT_EQ_INT(1, c_Array_Length(arr));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_Resize(arr, 1));
|
||||
ASSERT_INT_EQ(1, c_Array_Length(arr));
|
||||
|
||||
// Index 1 should now be unreachable / out of bounds
|
||||
C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Get(arr, 1, &res_ptr));
|
||||
ASSERT_INT_EQ(C_ERR_PARAM, c_Array_Get(arr, 1, &res_ptr));
|
||||
|
||||
c_Array_Delete(&arr);
|
||||
}
|
||||
|
||||
// 5. Deep Copy Execution Verification
|
||||
void test_array_copy_and_copy_to(void) {
|
||||
static void test_array_copy_and_copy_to(void) {
|
||||
c_Array_t* source = c_Array_New(3, sizeof(int));
|
||||
int a = 11, b = 22, c = 33;
|
||||
c_Array_Put(source, 0, &a);
|
||||
@@ -100,21 +100,21 @@ void test_array_copy_and_copy_to(void) {
|
||||
|
||||
// Test c_Array_Copy (creates a new array object on the heap)
|
||||
c_Array_t* copied_arr = c_Array_Copy(source, 2); // copy only first 2 items
|
||||
C_ASSERT_PTR_NOT_NULL(copied_arr);
|
||||
C_ASSERT_EQ_INT(2, c_Array_Length(copied_arr));
|
||||
ASSERT_PTR_NOT_NULL(copied_arr);
|
||||
ASSERT_INT_EQ(2, c_Array_Length(copied_arr));
|
||||
|
||||
void* data_ptr = NULL;
|
||||
c_Array_Get(copied_arr, 1, &data_ptr);
|
||||
C_ASSERT_EQ_INT(22, *(int*)data_ptr);
|
||||
ASSERT_INT_EQ(22, *(int*)data_ptr);
|
||||
|
||||
// Test c_Array_CopyTo (copies into an already initialized destination)
|
||||
c_Array_t dest;
|
||||
c_Array_Init(&dest, 3, sizeof(int));
|
||||
|
||||
C_ASSERT_EQ_INT(C_SUCCESS, c_Array_CopyTo(source, &dest));
|
||||
ASSERT_INT_EQ(C_SUCCESS, c_Array_CopyTo(source, &dest));
|
||||
|
||||
c_Array_Get(&dest, 2, &data_ptr);
|
||||
C_ASSERT_EQ_INT(33, *(int*)data_ptr);
|
||||
ASSERT_INT_EQ(33, *(int*)data_ptr);
|
||||
|
||||
// Clean up all resources
|
||||
c_Array_Delete(&copied_arr);
|
||||
@@ -122,20 +122,16 @@ void test_array_copy_and_copy_to(void) {
|
||||
c_Array_Delete(&source);
|
||||
}
|
||||
|
||||
/* ========================================================================== */
|
||||
/* SUITE BINDINGS & ENTRY MAIN */
|
||||
/* ========================================================================== */
|
||||
static
|
||||
void array_data_structure_suite(void) {
|
||||
C_TEST_CASE_RUN(test_array_stack_init_destroy);
|
||||
C_TEST_CASE_RUN(test_array_heap_new_delete);
|
||||
C_TEST_CASE_RUN(test_array_put_and_get);
|
||||
C_TEST_CASE_RUN(test_array_resize);
|
||||
C_TEST_CASE_RUN(test_array_copy_and_copy_to);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
C_TEST_SUITE_RUN(array_data_structure_suite);
|
||||
C_TEST_FRAME_REPORT();
|
||||
return (g_test_ctx.tests_passed == g_test_ctx.tests_run) ? 0 : 1;
|
||||
TEST_START(array_data_structure_suite);
|
||||
|
||||
RUN_TEST(test_array_stack_init_destroy);
|
||||
RUN_TEST(test_array_heap_new_delete);
|
||||
RUN_TEST(test_array_put_and_get);
|
||||
RUN_TEST(test_array_resize);
|
||||
RUN_TEST(test_array_copy_and_copy_to);
|
||||
|
||||
TEST_REPORT();
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
@@ -4,7 +4,8 @@
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
#define DEFAULT_INITIAL_CAPACITY 4
|
||||
#define DEFAULT_INITIAL_CAPACITY 4
|
||||
#define MIN_SHRINK_CAPACITY 4
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
@@ -13,14 +14,17 @@ c_err_t c_ArrayList_Init(c_ArrayList_t* self, c_size_t obj_size, c_size_t capaci
|
||||
if (!self || obj_size == 0) return C_ERR_PARAM;
|
||||
|
||||
self->obj_size = (int)obj_size;
|
||||
self->capacity = (capacity > 0) ? capacity : DEFAULT_INITIAL_CAPACITY;
|
||||
self->size = 0;
|
||||
self->capacity = capacity;
|
||||
|
||||
// 分配連續記憶體空間:容量 * 單個物件大小
|
||||
self->array = C_ALLOC(self->capacity * self->obj_size);
|
||||
if (!self->array) {
|
||||
self->capacity = 0;
|
||||
return C_ERR_NOMEM;
|
||||
if (capacity==0) {
|
||||
self->array = NULL;
|
||||
}else {
|
||||
self->array = C_ALLOC(self->capacity * self->obj_size);
|
||||
if (!self->array) {
|
||||
self->capacity = 0;
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
@@ -35,11 +39,11 @@ void c_ArrayList_Destroy(c_ArrayList_t* self) {
|
||||
}
|
||||
|
||||
c_err_t c_ArrayList_Add(c_ArrayList_t* self, void* obj) {
|
||||
if (!self || !self->array || !obj) return C_ERR_PARAM;
|
||||
if (!self || !obj) return C_ERR_PARAM;
|
||||
|
||||
// 動態擴容邏輯
|
||||
if (self->size >= self->capacity) {
|
||||
const c_size_t new_capacity = self->capacity<<1;
|
||||
const c_size_t new_capacity = (self->capacity==0)?DEFAULT_INITIAL_CAPACITY:(self->capacity<<1);
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (!new_array) {
|
||||
return C_ERR_NOMEM;
|
||||
@@ -79,6 +83,19 @@ c_err_t c_ArrayList_Remove(c_ArrayList_t* self, c_size_t index) {
|
||||
}
|
||||
|
||||
self->size--;
|
||||
|
||||
// 策略:当实际大小少于等于容量的 1/4,且缩容后的容量不低于设定的最小阈值时触发
|
||||
if (self->size > 0 && self->size <= (self->capacity >> 2)) {
|
||||
c_size_t new_capacity = self->capacity >> 1; // 容量减半
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (new_array) { // 如果 realloc 失败不影响原有数据安全,这里采用安全赋值
|
||||
self->array = new_array;
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ typedef struct {
|
||||
c_size_t size;
|
||||
}c_ArrayList_t;
|
||||
|
||||
#define c_ArrayList(obj_size) ((c_ArrayList_t) { 0, (obj_size), 0, 0 })
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
@@ -30,4 +32,5 @@ void* c_ArrayList_Get(c_ArrayList_t* self, c_size_t index);
|
||||
|
||||
c_err_t c_ArrayList_Remove(c_ArrayList_t* self, c_size_t index);
|
||||
|
||||
|
||||
#endif /*INCLUDED_C_ARRAYLIST_H*/
|
||||
|
||||
+107
-16
@@ -10,13 +10,13 @@ struct Vector2D {
|
||||
/* ==============================================================================
|
||||
* 🧪 测试全新通用 void* 缓冲区的初始化、自动扩容与随机 Remove 重排全周期行为
|
||||
* ============================================================================== */
|
||||
C_TEST_CASE(test_array_list_full_lifecycle_and_removal)
|
||||
TEST_CASE(test_array_list_full_lifecycle_and_removal)
|
||||
{
|
||||
c_ArrayList_t list;
|
||||
/* 1. 初始化:装载自定义 Vector2D 结构体,初始最大可容纳元素数量卡死限制为 2 */
|
||||
c_err_t init_err = c_ArrayList_Init(&list, sizeof(struct Vector2D), 2);
|
||||
C_ASSERT_INT_EQ(init_err, C_ERR_OK, "弹性自愈 ArrayList 初始化成功");
|
||||
C_ASSERT_INT_EQ(list.size, 0, "有效初始记录必须为 0");
|
||||
ASSERT_INT_EQ_MSG(init_err, C_ERR_OK, "弹性自愈 ArrayList 初始化成功");
|
||||
ASSERT_INT_EQ_MSG(list.size, 0, "有效初始记录必须为 0");
|
||||
|
||||
struct Vector2D vec1 = { 11.1, 22.2 };
|
||||
struct Vector2D vec2 = { 33.3, 44.4 };
|
||||
@@ -28,38 +28,129 @@ C_TEST_CASE(test_array_list_full_lifecycle_and_removal)
|
||||
|
||||
/* 🎯 扩容看点:此时满员,第三次写入将强行逼迫池子在内部启动 2 -> 4 自动翻倍重分配 */
|
||||
c_err_t add_err = c_ArrayList_Add(&list, &vec3);
|
||||
C_ASSERT_INT_EQ(add_err, C_ERR_OK, "耗尽时追加写入,分配器必须完成全自动无感自愈扩容");
|
||||
C_ASSERT_INT_EQ(list.size, 3, "当前有效装载数递增至 3");
|
||||
ASSERT_INT_EQ_MSG(add_err, C_ERR_OK, "耗尽时追加写入,分配器必须完成全自动无感自愈扩容");
|
||||
ASSERT_INT_EQ_MSG(list.size, 3, "当前有效装载数递增至 3");
|
||||
|
||||
/* 3. 验证数据内容的物理隔离完整度 */
|
||||
struct Vector2D* p_check1 = (struct Vector2D*)c_ArrayList_Get(&list, 1);
|
||||
C_ASSERT(p_check1 != NULL, "随机读取索引 1 节点成功");
|
||||
C_ASSERT_DOUBLE_EQ(p_check1->u, 33.3, "重分配内存迁移后,原有位置 1 的数据必须毫发无损");
|
||||
ASSERT_MSG(p_check1 != NULL, "随机读取索引 1 节点成功");
|
||||
ASSERT_DOUBLE_EQ_MSG(p_check1->u, 33.3, "重分配内存迁移后,原有位置 1 的数据必须毫发无损");
|
||||
|
||||
/* 4. 🛠️ 高能测试点:随机抹除中间位置 1 的节点 (即删掉 vec2)
|
||||
预期结果:原位置 2 的 vec3 ({55.5, 66.6}) 必须在 O(N) 速度下向前平移,填补顶替空位 1 */
|
||||
c_err_t remove_err = c_ArrayList_Remove(&list, 1);
|
||||
C_ASSERT_INT_EQ(remove_err, C_ERR_OK, "执行中途位置随机移除成功");
|
||||
C_ASSERT_INT_EQ(list.size, 2, "移除数据后,有效数据计数平滑扣减为 2");
|
||||
ASSERT_INT_EQ_MSG(remove_err, C_ERR_OK, "执行中途位置随机移除成功");
|
||||
ASSERT_INT_EQ_MSG(list.size, 2, "移除数据后,有效数据计数平滑扣减为 2");
|
||||
|
||||
/* 5. 终极完整性断言:现在去 Get 原本的位置 1 */
|
||||
struct Vector2D* p_relocated = (struct Vector2D*)c_ArrayList_Get(&list, 1);
|
||||
C_ASSERT(p_relocated != NULL, "重新获取平移顶替后的位置 1 节点成功");
|
||||
ASSERT_MSG(p_relocated != NULL, "重新获取平移顶替后的位置 1 节点成功");
|
||||
|
||||
/* 核心断言:原位置 2 的数据现在必须完美出现在位置 1 线上,且精度不发生移位错乱! */
|
||||
C_ASSERT_DOUBLE_EQ(p_relocated->u, 55.5, "元素向前滑动对齐后,浮点特征完好无损");
|
||||
C_ASSERT_DOUBLE_EQ(p_relocated->v, 66.6, "元素向前滑动对齐后,浮点特征完好无损");
|
||||
ASSERT_DOUBLE_EQ_MSG(p_relocated->u, 55.5, "元素向前滑动对齐后,浮点特征完好无损");
|
||||
ASSERT_DOUBLE_EQ_MSG(p_relocated->v, 66.6, "元素向前滑动对齐后,浮点特征完好无损");
|
||||
|
||||
/* 6. 边界越界捕获安全防御线 */
|
||||
void* invalid_ptr = c_ArrayList_Get(&list, 2); /* 此时由于删了一个,索引 2 已变为空旷越界区 */
|
||||
C_ASSERT(invalid_ptr == NULL, "越界获取已经被逻辑截断删除的位置必须安全回传 NULL");
|
||||
ASSERT_MSG(invalid_ptr == NULL, "越界获取已经被逻辑截断删除的位置必须安全回传 NULL");
|
||||
|
||||
c_ArrayList_Destroy(&list);
|
||||
}
|
||||
|
||||
static void test_list_auto_expansion() {
|
||||
int data[] = {10, 20, 30};
|
||||
c_ArrayList_t list;
|
||||
c_ArrayList_Init(&list, sizeof(int), 2);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
c_ArrayList_Add(&list, &data[i]);
|
||||
}
|
||||
|
||||
// 验证容量是否翻倍 (2 << 1 = 4)
|
||||
ASSERT_INT_EQ_MSG(4, list.capacity, "Capacity should double to 4");
|
||||
ASSERT_INT_EQ_MSG(3, list.size, "Size should be 3");
|
||||
|
||||
// 验证最后一个元素有没有因为扩容导致内存搬移出错
|
||||
int* p3 = (int*)c_ArrayList_Get(&list, 2);
|
||||
ASSERT_MSG(p3 != NULL, "Expanded element should be accessible");
|
||||
ASSERT_INT_EQ_MSG(30, *p3, "Expanded element data corruption");
|
||||
}
|
||||
|
||||
static void test_list_remove_element() {
|
||||
int data[] = {11, 22, 33, 44};
|
||||
c_ArrayList_t list;
|
||||
c_ArrayList_Init(&list, sizeof(int), 2);
|
||||
|
||||
for(int i = 0; i < 4; i++) c_ArrayList_Add(&list, &data[i]);
|
||||
|
||||
// 删除索引为 1 的元素 (即数字 22)
|
||||
c_err_t err = c_ArrayList_Remove(&list, 1);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Remove operational failure");
|
||||
ASSERT_INT_EQ_MSG(3, list.size, "Size should decrease to 3");
|
||||
|
||||
// 此时索引 1 应该变成了 33,索引 2 应该变成了 44
|
||||
int* p1 = (int*)c_ArrayList_Get(&list, 1);
|
||||
int* p2 = (int*)c_ArrayList_Get(&list, 2);
|
||||
|
||||
ASSERT_INT_EQ_MSG(33, *p1, "Element forward-shift error at index 1");
|
||||
ASSERT_INT_EQ_MSG(44, *p2, "Element forward-shift error at index 2");
|
||||
|
||||
// 检查获取越界索引是否安全返回 NULL
|
||||
ASSERT_MSG(c_ArrayList_Get(&list, 3) == NULL, "Out of bounds should return NULL");
|
||||
}
|
||||
|
||||
static void test_list_zero_initial_capacity() {
|
||||
c_ArrayList_t zero_list;
|
||||
c_ArrayList_Init(&zero_list, sizeof(int), 0);
|
||||
|
||||
int val = 99;
|
||||
// 如果你没有按照上方提示修复缺陷①,该断言将会失败(期望返回 OK 却返回了 PARAM 错误)
|
||||
c_err_t err = c_ArrayList_Add(&zero_list, &val);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Add failed when initial capacity is 0 (Bug ① Triggered!)");
|
||||
|
||||
c_ArrayList_Destroy(&zero_list);
|
||||
}
|
||||
|
||||
static void test_list_auto_shrink() {
|
||||
// 1. 连续添加 9 个元素触发扩容
|
||||
// 初始化 0 -> 扩容到 4 -> 扩容到 8 -> 扩容到 16
|
||||
c_ArrayList_t list;
|
||||
c_ArrayList_Init(&list, sizeof(int), 2);
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
c_ArrayList_Add(&list, &i);
|
||||
}
|
||||
ASSERT_INT_EQ_MSG(16, list.capacity, "Capacity should expand up to 16");
|
||||
|
||||
// 2. 依次删除元素,降低 size 以试图触发 1/4 缩容临界点
|
||||
// 当 size 减少到 4 时 (即 16 / 4), 应该触发缩容:16 减半变成 8
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c_ArrayList_Remove(&list, 0); // 总是移除首个元素
|
||||
}
|
||||
|
||||
// 此时移除了 5 个,剩下 4 个元素
|
||||
ASSERT_INT_EQ_MSG(4, list.size, "Current size should be 4");
|
||||
ASSERT_INT_EQ_MSG(8, list.capacity, "Capacity should automatically shrink to 8");
|
||||
|
||||
// 3. 继续删除,观察是否会由于低于最小阈值(4)而停止缩容
|
||||
for (int i = 0; i < 3; i++) {
|
||||
c_ArrayList_Remove(&list, 0);
|
||||
}
|
||||
// 此时只剩 1 个元素了 (1 <= 8/4),但由于 MIN_SHRINK_CAPACITY = 4 限制,容量不应该再减半到 2
|
||||
ASSERT_INT_EQ_MSG(1, list.size, "Current size should be 1");
|
||||
ASSERT_INT_EQ_MSG(2, list.capacity, "Capacity should hold at 2 to prevent thrashing");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
C_TEST_SUITE_BEGIN(BaseArrayListNewSpecificationTestSuite)
|
||||
C_RUN_TEST_CASE(test_array_list_full_lifecycle_and_removal);
|
||||
C_TEST_SUITE_END()
|
||||
TEST_START(BaseArrayListNewSpecificationTestSuite);
|
||||
|
||||
RUN_TEST(test_array_list_full_lifecycle_and_removal);
|
||||
RUN_TEST(test_list_auto_expansion);
|
||||
RUN_TEST(test_list_remove_element);
|
||||
RUN_TEST(test_list_zero_initial_capacity);
|
||||
RUN_TEST(test_list_auto_shrink);
|
||||
|
||||
TEST_REPORT();
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <c_ArrayQueue.h>
|
||||
#include <c_Memory.h>
|
||||
|
||||
#define DEFAULT_INITIAL_CAPACITY 4
|
||||
#define DEFAULT_INITIAL_CAPACITY 4
|
||||
#define MIN_SHRINK_CAPACITY 4
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
@@ -72,6 +73,18 @@ c_err_t c_ArrayQueue_Pop(c_ArrayQueue_t* self, void* obj) {
|
||||
}
|
||||
|
||||
self->size--;
|
||||
|
||||
if (self->size > 0 && self->size <= (self->capacity >> 2)) {
|
||||
c_size_t new_capacity = self->capacity >> 1; // 容量减半
|
||||
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (new_array) { // 如果 realloc 失败不影响原有数据安全,这里采用安全赋值
|
||||
self->array = new_array;
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "c_ArrayQueue.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <c_Test.h>
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
static c_ArrayQueue_t g_queue;
|
||||
|
||||
// 启动环境:初始化一个初始容量为 4 的 int 类型队列
|
||||
void setup_queue() {
|
||||
c_err_t err = c_ArrayQueue_Init(&g_queue, sizeof(int), 4);
|
||||
if (err != C_ERR_OK) {
|
||||
printf(" " COLOR_RED "[ERROR] Queue Setup failed!" COLOR_RESET "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 清理环境:安全销毁队列
|
||||
void teardown_queue() {
|
||||
c_ArrayQueue_Destroy(&g_queue);
|
||||
}
|
||||
|
||||
// 用例 1:测试常规入队、查看对头、出队(FIFO 基础逻辑)
|
||||
void test_queue_push_pop_basic() {
|
||||
int v1 = 10, v2 = 20, v3 = 30;
|
||||
|
||||
// 入队 3 个元素
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayQueue_Push(&g_queue, &v1), "Push 10 failed");
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayQueue_Push(&g_queue, &v2), "Push 20 failed");
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayQueue_Push(&g_queue, &v3), "Push 30 failed");
|
||||
|
||||
// 检查大小
|
||||
ASSERT_INT_EQ_MSG(3, g_queue.size, "Size should be 3");
|
||||
|
||||
// Peek 检查队头(应该依然是 10,且不弹出元素)
|
||||
int* front_ptr = (int*)c_ArrayQueue_Peek(&g_queue);
|
||||
ASSERT_MSG(front_ptr != NULL, "Peek should not return NULL");
|
||||
ASSERT_INT_EQ_MSG(10, *front_ptr, "Peek value mismatches");
|
||||
|
||||
// 开始 Pop 出队,验证 FIFO 顺序
|
||||
int out_val = 0;
|
||||
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayQueue_Pop(&g_queue, &out_val), "Pop 1 failed");
|
||||
ASSERT_INT_EQ_MSG(10, out_val, "First out should be 10");
|
||||
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayQueue_Pop(&g_queue, &out_val), "Pop 2 failed");
|
||||
ASSERT_INT_EQ_MSG(20, out_val, "Second out should be 20");
|
||||
|
||||
ASSERT_INT_EQ_MSG(2, g_queue.capacity, "Capacity management check"); // 选测:如果你内部写了 Pop 自动缩容
|
||||
ASSERT_INT_EQ_MSG(1, g_queue.size, "Size should drop to 1");
|
||||
}
|
||||
|
||||
|
||||
// 用例 2:测试队列在空(Empty)或未初始化状态下的防御表现
|
||||
void test_queue_empty_bounds() {
|
||||
int out_val = 999;
|
||||
|
||||
// 空队列直接 Pop 应该报错
|
||||
c_err_t err = c_ArrayQueue_Pop(&g_queue, &out_val);
|
||||
ASSERT_MSG(err != C_ERR_OK, "Pop on empty queue should return an error code");
|
||||
ASSERT_INT_EQ_MSG(999, out_val, "Output value should remain untouched on failure");
|
||||
|
||||
// 空队列 Peek 应该返回 NULL
|
||||
ASSERT_MSG(c_ArrayQueue_Peek(&g_queue) == NULL, "Peek on empty queue must return NULL");
|
||||
}
|
||||
|
||||
|
||||
// 用例 3:测试循环环绕与动态扩容(如果是循环队列,该用例极度核心)
|
||||
void test_queue_wrap_around_and_expand() {
|
||||
int v = 0;
|
||||
int out = 0;
|
||||
|
||||
// 1. 先把初始容量 4 填满
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
v = i * 10; // 10, 20, 30, 40
|
||||
c_ArrayQueue_Push(&g_queue, &v);
|
||||
}
|
||||
|
||||
// 2. 弹出 2 个元素(释放前面两个格子的空间,触发头部指针往后移动)
|
||||
c_ArrayQueue_Pop(&g_queue, &out); // 弹出 10
|
||||
c_ArrayQueue_Pop(&g_queue, &out); // 弹出 20
|
||||
|
||||
// 3. 再次塞入 2 个元素(如果是循环队列,这俩元素会被存到刚才释放的 0 和 1 索引槽位)
|
||||
v = 50; c_ArrayQueue_Push(&g_queue, &v);
|
||||
v = 60; c_ArrayQueue_Push(&g_queue, &v);
|
||||
|
||||
// 4. 此时队列满(包含 30, 40, 50, 60),再次 Push 触发扩容
|
||||
v = 70;
|
||||
c_err_t err = c_ArrayQueue_Push(&g_queue, &v);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, "Push triggering growth failed");
|
||||
ASSERT_MSG(g_queue.capacity > 4, "Queue failed to expand capacity");
|
||||
|
||||
// 5. 依次全部弹出,验证即使经历过“环绕”与“扩容搬移内存”,FIFO 序列依旧保持绝对准确
|
||||
int expected_sequence[] = {30, 40, 50, 60, 70};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c_ArrayQueue_Pop(&g_queue, &out);
|
||||
char msg[100];
|
||||
sprintf(msg, "Sequence broke at check-index [%d], expected %d", i, expected_sequence[i]);
|
||||
ASSERT_INT_EQ_MSG(expected_sequence[i], out, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 用例 4:测试任意位置删除(c_ArrayQueue_Remove)
|
||||
void test_queue_remove_by_index() {
|
||||
int values[] = {100, 200, 300, 400};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
c_ArrayQueue_Push(&g_queue, &values[i]);
|
||||
}
|
||||
|
||||
// 尝试删除越界索引(当前有 4 个元素,有效索引为 0~3,删除 5 应该失败)
|
||||
c_err_t err_out = c_ArrayQueue_Remove(&g_queue, 5);
|
||||
ASSERT_MSG(err_out != C_ERR_OK, "Remove out of bounds should fail");
|
||||
|
||||
// 删除当前队列中的中间元素(索引 1,即删除 200)
|
||||
c_err_t err_ok = c_ArrayQueue_Remove(&g_queue, 1);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err_ok, "Remove element failed");
|
||||
ASSERT_INT_EQ_MSG(3, g_queue.size, "Size should be 3 after removal");
|
||||
|
||||
// 依次 Pop 验证剩下的元素顺序是否已经平滑前移(应该是 100 -> 300 -> 400)
|
||||
int out = 0;
|
||||
|
||||
c_ArrayQueue_Pop(&g_queue, &out);
|
||||
ASSERT_INT_EQ_MSG(100, out, "First element should still be 100");
|
||||
|
||||
c_ArrayQueue_Pop(&g_queue, &out);
|
||||
ASSERT_INT_EQ_MSG(300, out, "Element 300 should shift forward to index 1");
|
||||
|
||||
c_ArrayQueue_Pop(&g_queue, &out);
|
||||
ASSERT_INT_EQ_MSG(400, out, "Element 400 should shift forward to index 2");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
int main(int argc, char** argv){
|
||||
TEST_START(Starting Unit Tests);
|
||||
|
||||
RUN_TEST_FIXTURE(test_queue_push_pop_basic, setup_queue, teardown_queue);
|
||||
RUN_TEST_FIXTURE(test_queue_empty_bounds, setup_queue, teardown_queue);
|
||||
RUN_TEST_FIXTURE(test_queue_wrap_around_and_expand, setup_queue, teardown_queue);
|
||||
RUN_TEST_FIXTURE(test_queue_remove_by_index, setup_queue, teardown_queue);
|
||||
|
||||
// 打印最终统计报告
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -6,14 +6,18 @@
|
||||
c_err_t c_ArrayStack_Init(c_ArrayStack_t* self, int obj_size, c_size_t capacity) {
|
||||
if (!self || obj_size <= 0) return C_ERR_PARAM;
|
||||
|
||||
self->obj_size = obj_size;
|
||||
self->capacity = (capacity > 0) ? capacity : DEFAULT_INITIAL_CAPACITY;
|
||||
self->obj_size = (int)obj_size;
|
||||
self->size = 0;
|
||||
self->capacity = capacity;
|
||||
|
||||
self->array = C_ALLOC(self->capacity * self->obj_size);
|
||||
if (!self->array) {
|
||||
self->capacity = 0;
|
||||
return C_ERR_NOMEM;
|
||||
if (capacity==0) {
|
||||
self->array = NULL;
|
||||
}else {
|
||||
self->array = C_ALLOC(self->capacity * self->obj_size);
|
||||
if (!self->array) {
|
||||
self->capacity = 0;
|
||||
return C_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
@@ -33,7 +37,7 @@ c_err_t c_ArrayStack_Push(c_ArrayStack_t* self, void* obj) {
|
||||
if (!self || !self->array || !obj) return C_ERR_PARAM;
|
||||
|
||||
if (self->size >= self->capacity) {
|
||||
const c_size_t new_capacity = self->capacity << 1;
|
||||
const c_size_t new_capacity = (self->capacity==0)?DEFAULT_INITIAL_CAPACITY:(self->capacity<<1);
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (!new_array) {
|
||||
return C_ERR_NOMEM;
|
||||
@@ -61,6 +65,16 @@ c_err_t c_ArrayStack_Pop(c_ArrayStack_t* self, void* obj) {
|
||||
memcpy(obj, pop_src, self->obj_size);
|
||||
|
||||
self->size--;
|
||||
|
||||
if (self->size > 0 && self->size <= (self->capacity >> 2)) {
|
||||
c_size_t new_capacity = self->capacity >> 1;
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (new_array) {
|
||||
self->array = new_array;
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
@@ -82,6 +96,16 @@ c_err_t c_ArrayStack_Remove(c_ArrayStack_t* self, c_size_t index) {
|
||||
}
|
||||
|
||||
self->size--;
|
||||
|
||||
if (self->size > 0 && self->size <= (self->capacity >> 2)) {
|
||||
c_size_t new_capacity = self->capacity >> 1;
|
||||
void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size);
|
||||
if (new_array) {
|
||||
self->array = new_array;
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "c_ArrayStack.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <c_Test.h>
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
static c_ArrayStack_t g_stack;
|
||||
|
||||
// 启动环境:初始化一个初始容量为 2 的 int 类型栈
|
||||
static void setup_stack() {
|
||||
c_err_t err = c_ArrayStack_Init(&g_stack, sizeof(int), 2);
|
||||
if (err != C_ERR_OK) {
|
||||
printf(" " COLOR_RED "[ERROR] Stack Setup failed!" COLOR_RESET "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 清理环境:安全销毁栈,杜绝内存泄漏
|
||||
static void teardown_stack() {
|
||||
c_ArrayStack_Destroy(&g_stack);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
// 用例 1:测试 LIFO(后进先出)核心逻辑、IsEmpty 状态及 Peek 观察
|
||||
static void test_stack_push_pop_lifo() {
|
||||
// 初始状态应该为空
|
||||
ASSERT_MSG(c_ArrayStack_IsEmpty(&g_stack) == true, "Stack should be empty initially");
|
||||
|
||||
int v1 = 111, v2 = 222;
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayStack_Push(&g_stack, &v1), "Push 111 failed");
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayStack_Push(&g_stack, &v2), "Push 222 failed");
|
||||
|
||||
// 此时不应该为空,大小应为 2
|
||||
ASSERT_MSG(c_ArrayStack_IsEmpty(&g_stack) == false, "Stack should not be empty");
|
||||
ASSERT_INT_EQ_MSG(2, g_stack.size, "Stack size should be 2");
|
||||
|
||||
// 测试 Peek:应该看到最后压入的 222,且栈大小不变
|
||||
int* top_ptr = (int*)c_ArrayStack_Peek(&g_stack);
|
||||
ASSERT_MSG(top_ptr != NULL, "Peek should not return NULL");
|
||||
ASSERT_INT_EQ_MSG(222, *top_ptr, "Peek value should be 222");
|
||||
ASSERT_INT_EQ_MSG(2, g_stack.size, "Size must remain 2 after peek");
|
||||
|
||||
// 测试 Pop:验证 LIFO 顺序
|
||||
int out_val = 0;
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayStack_Pop(&g_stack, &out_val), "First pop failed");
|
||||
ASSERT_INT_EQ_MSG(222, out_val, "First popped value should be 222 (LIFO)");
|
||||
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, c_ArrayStack_Pop(&g_stack, &out_val), "Second pop failed");
|
||||
ASSERT_INT_EQ_MSG(111, out_val, "Second popped value should be 111");
|
||||
|
||||
// 最终应该重新变为空栈
|
||||
ASSERT_MSG(c_ArrayStack_IsEmpty(&g_stack) == true, "Stack should be empty after popping all elements");
|
||||
}
|
||||
|
||||
|
||||
// 用例 2:测试空栈(Empty)的边界防御表现
|
||||
static void test_stack_empty_bounds() {
|
||||
int dummy = 999;
|
||||
|
||||
// 空栈执行 Pop 应该安全报错(例如返回 C_ERR_EMPTY 或非 OK 状态)
|
||||
c_err_t err = c_ArrayStack_Pop(&g_stack, &dummy);
|
||||
ASSERT_MSG(err != C_ERR_OK, "Pop on empty stack should return an error code");
|
||||
ASSERT_INT_EQ_MSG(999, dummy, "Output buffer should remain unchanged on failure");
|
||||
|
||||
// 空栈执行 Peek 应该安全返回 NULL
|
||||
ASSERT_MSG(c_ArrayStack_Peek(&g_stack) == NULL, "Peek on empty stack must return NULL");
|
||||
}
|
||||
|
||||
|
||||
// 用例 3:动态自动扩容测试
|
||||
static void test_stack_auto_expansion() {
|
||||
// 初始容量设为了 2,连续压入 4 个数据触发自动扩容
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
int val = i * 10;
|
||||
c_err_t err = c_ArrayStack_Push(&g_stack, &val);
|
||||
char msg[64];
|
||||
sprintf(msg, "Pushing element %d failed", val);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err, msg);
|
||||
}
|
||||
|
||||
// 校验容量是否增长
|
||||
ASSERT_MSG(g_stack.capacity > 2, "Stack capacity should have grown");
|
||||
ASSERT_INT_EQ_MSG(4, g_stack.size, "Stack size should be 4");
|
||||
|
||||
// 倒序弹出校验,确保扩容重构内存后历史数据未损坏
|
||||
int out_val = 0;
|
||||
int expected_vals[] = {40, 30, 20, 10};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
c_ArrayStack_Pop(&g_stack, &out_val);
|
||||
char msg[64];
|
||||
sprintf(msg, "Mismatched LIFO element at step %d", i);
|
||||
ASSERT_INT_EQ_MSG(expected_vals[i], out_val, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 用例 4:测试从任意指定索引删除元素(c_ArrayStack_Remove)
|
||||
static void test_stack_remove_by_index() {
|
||||
// 压入底 -> 顶:10, 20, 30, 40
|
||||
// 索引映射:index 0 -> 10, index 1 -> 20, index 2 -> 30, index 3 -> 40
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
int val = i * 10;
|
||||
c_ArrayStack_Push(&g_stack, &val);
|
||||
}
|
||||
|
||||
// 1. 测试越界删除防御(有效索引为 0~3)
|
||||
c_err_t err_invalid = c_ArrayStack_Remove(&g_stack, 4);
|
||||
ASSERT_MSG(err_invalid != C_ERR_OK, "Remove out of bounds should fail");
|
||||
|
||||
// 2. 删除中间的元素:索引 1 (对应数字 20)
|
||||
c_err_t err_ok = c_ArrayStack_Remove(&g_stack, 1);
|
||||
ASSERT_INT_EQ_MSG(C_ERR_OK, err_ok, "Remove middle element failed");
|
||||
ASSERT_INT_EQ_MSG(3, g_stack.size, "Size should drop to 3 after remove");
|
||||
|
||||
// 3. 验证删除后的整体结构。由于移除了 20,剩下的数组结构应为:10, 30, 40
|
||||
// 按照栈的 LIFO 弹出顺序,依次拿到的应该是 40 -> 30 -> 10
|
||||
int out_val = 0;
|
||||
|
||||
c_ArrayStack_Pop(&g_stack, &out_val);
|
||||
ASSERT_INT_EQ_MSG(40, out_val, "Top should still be 40");
|
||||
|
||||
c_ArrayStack_Pop(&g_stack, &out_val);
|
||||
ASSERT_INT_EQ_MSG(30, out_val, "Next should be 30 (since 20 was removed)");
|
||||
|
||||
c_ArrayStack_Pop(&g_stack, &out_val);
|
||||
ASSERT_INT_EQ_MSG(10, out_val, "Bottom element should be 10");
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
int main(int argc, char** argv){
|
||||
|
||||
TEST_START(Starting Unit Tests);
|
||||
|
||||
// 运行需要内存环境的用例
|
||||
RUN_TEST_FIXTURE(test_stack_push_pop_lifo, setup_stack, teardown_stack);
|
||||
RUN_TEST_FIXTURE(test_stack_empty_bounds, setup_stack, teardown_stack);
|
||||
RUN_TEST_FIXTURE(test_stack_auto_expansion, setup_stack, teardown_stack);
|
||||
RUN_TEST_FIXTURE(test_stack_remove_by_index, setup_stack, teardown_stack);
|
||||
|
||||
|
||||
// 打印最终统计报告
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ c_err_t c_FastByteRingBuffer_Init(c_FastByteRingBuffer_t* self, c_size_t capacit
|
||||
self->tail = 0;
|
||||
self->is_full = C_FALSE;
|
||||
|
||||
self->buffer = (uint8_t*)malloc(self->capacity);
|
||||
self->buffer = (uint8_t*)C_ALLOC(self->capacity);
|
||||
if (!self->buffer) {
|
||||
self->capacity = 0;
|
||||
self->mask = 0;
|
||||
@@ -339,7 +339,7 @@ c_index_t c_FastByteRingBuffer_IndexOfByte(const c_FastByteRingBuffer_t* self, u
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
}
|
||||
return C_ERR_FAIL;
|
||||
return C_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
c_index_t c_FastByteRingBuffer_IndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "c_FastByteRingBuffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
#define RUN_TEST_CASE(test_func) \
|
||||
do { \
|
||||
@@ -126,7 +131,7 @@ static void test_ring_buffer_index_searching(void) {
|
||||
|
||||
// 1. Single byte search lookup match
|
||||
assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x33) == 2); // Relative offset index 2 from head
|
||||
assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_NOT_FOUND);
|
||||
assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_NOTFOUND);
|
||||
|
||||
// 2. Pattern buffer sequence scan (testing across structural wrap-around edge boundaries)
|
||||
uint8_t search_pattern[] = {0x44, 0x55, 0x66};
|
||||
@@ -206,9 +211,9 @@ static void test_relative_random_access(void) {
|
||||
assert(extracted_byte == 0x50); // Newest unread byte check
|
||||
|
||||
// 2. Validate bounds-checking flags
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_INVALID_PARAM);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUTOFBOUND);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUTOFBOUND);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_PARAM);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
@@ -244,7 +249,7 @@ static void test_conditional_is_validator(void) {
|
||||
|
||||
static void test_ring_buffer_memcmp(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 6) == C_ERR_INVALID_PARAM); // Capacity = 6
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 6) == C_ERR_PARAM); // Capacity = 6
|
||||
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 6
|
||||
uint8_t payload[] = {0x00, 0x11, 0x22, 0x33};
|
||||
@@ -274,9 +279,9 @@ static void test_ring_buffer_memcmp(void) {
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 1, check_mismatch, 4) != 0); // Identifies internal divergence
|
||||
|
||||
// 4. Bounds and parameter checks
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUT_OF_BOUNDS); // Request width overflows content
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUT_OF_BOUNDS); // Start pointer invalid
|
||||
assert(c_FastByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_INVALID_PARAM);
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUTOFBOUND); // Request width overflows content
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUTOFBOUND); // Start pointer invalid
|
||||
assert(c_FastByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_PARAM);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
+210
-1
@@ -3,14 +3,19 @@
|
||||
#include <windows.h>
|
||||
#include <io.h> // 提供 _get_osfhandle
|
||||
#include <direct.h>
|
||||
#include <dirent.h>
|
||||
#define C_ACCESS(path) _access(path, 0)
|
||||
#define C_MAKE_DIR(path) _mkdir(path) // Windows 下创建目录
|
||||
#define sys_rmdir(path) _rmdir(path)
|
||||
#define PATH_SEP '\\'
|
||||
#else
|
||||
#include <unistd.h> // 提供 fsync
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#define C_ACCESS(path) access(path, F_OK)
|
||||
#define C_MAKE_DIR(path) mkdir(path, 0755)
|
||||
#define sys_rmdir(path) rmdir(path)
|
||||
#define PATH_SEP '/'
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
@@ -276,4 +281,208 @@ c_err_t c_File_MkDirs(const char* path) {
|
||||
}
|
||||
|
||||
return C_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
c_err_t c_File_Rmdir(const char* path) {
|
||||
if (!path || path[0] == '\0') {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 调用操作系统底层移除目录的 API
|
||||
int result = sys_rmdir(path);
|
||||
|
||||
if (result == 0) {
|
||||
return C_ERR_OK; // 删除成功
|
||||
} else {
|
||||
// 删除失败(可能是由于目录不存在、无权限、或者目录非空)
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
c_err_t c_File_Rmdirs(const char* path) {
|
||||
if (!path || path[0] == '\0') {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
c_err_t status = C_ERR_OK;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// ==========================================
|
||||
// Windows 平台的递归删除实现
|
||||
// ==========================================
|
||||
char search_path[MAX_PATH];
|
||||
// Windows 检索目录需要追加 "\\*"
|
||||
snprintf(search_path, sizeof(search_path), "%s\\*", path);
|
||||
|
||||
WIN32_FIND_DATAA find_data;
|
||||
HANDLE h_find = FindFirstFileA(search_path, &find_data);
|
||||
|
||||
if (h_find == INVALID_HANDLE_VALUE) {
|
||||
// 如果目录根本不存在,或者无法打开,尝试直接当做文件 remove 移除(处理符号链接等边界)
|
||||
return remove(path) == 0 ? C_ERR_OK : C_ERR_FAIL;
|
||||
}
|
||||
|
||||
do {
|
||||
// 排除 Windows 的特殊目录 "." 和 ".."
|
||||
if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 拼接子项的完整路径
|
||||
char sub_path[MAX_PATH];
|
||||
snprintf(sub_path, sizeof(sub_path), "%s\\%s", path, find_data.cFileName);
|
||||
|
||||
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
// 如果子项是目录,递归调用
|
||||
status = c_File_Rmdirs(sub_path);
|
||||
} else {
|
||||
// 如果子项是普通文件,取消只读属性(防止因只读导致删除失败),然后将其删除
|
||||
SetFileAttributesA(sub_path, FILE_ATTRIBUTE_NORMAL);
|
||||
status = (DeleteFileA(sub_path) != 0) ? C_ERR_OK : C_ERR_FAIL;
|
||||
}
|
||||
|
||||
if (status != C_ERR_OK) {
|
||||
break;
|
||||
}
|
||||
} while (FindNextFileA(h_find, &find_data));
|
||||
|
||||
FindClose(h_find);
|
||||
|
||||
#else
|
||||
// ==========================================
|
||||
// Linux / macOS (POSIX) 平台的递归删除实现
|
||||
// ==========================================
|
||||
DIR* dir = opendir(path);
|
||||
if (!dir) {
|
||||
// 无法打开作为目录处理,尝试按普通文件物理删除
|
||||
return remove(path) == 0 ? C_ERR_OK : C_ERR_FAIL;
|
||||
}
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
// 排除 Linux 的特殊目录 "." 和 ".."
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 拼接子项的完整路径
|
||||
char sub_path[1024];
|
||||
snprintf(sub_path, sizeof(sub_path), "%s/%s", path, entry->d_name);
|
||||
|
||||
struct stat statbuf;
|
||||
if (stat(sub_path, &statbuf) == 0) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
// 如果子项是目录,递归调用
|
||||
status = c_File_Rmdirs(sub_path);
|
||||
} else {
|
||||
// 如果子项是文件,执行普通删除
|
||||
status = (remove(sub_path) == 0) ? C_ERR_OK : C_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (status != C_ERR_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
#endif
|
||||
|
||||
// ==========================================
|
||||
// 公共收尾:清空了内部所有子项后,最后删除外层空壳目录
|
||||
// ==========================================
|
||||
if (status == C_ERR_OK) {
|
||||
if (sys_rmdir(path) != 0) {
|
||||
status = C_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
c_err_t c_File_Delete(const char* fileName) {
|
||||
if (!fileName || fileName[0] == '\0') {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 标准 C 库的 remove 能够直接删除文件(在某些平台上也能删除空目录)
|
||||
int result = remove(fileName);
|
||||
|
||||
if (result == 0) {
|
||||
return C_ERR_OK; // 删除成功
|
||||
} else {
|
||||
// 删除失败(文件不存在、或无权限、或文件正被某些操作系统强锁占用)
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
#define COPY_BUFFER_SIZE 4096
|
||||
|
||||
c_err_t c_File_Copy(const char* srcPath, const char* destPath) {
|
||||
if (!srcPath || srcPath[0] == '\0' || !destPath || destPath[0] == '\0') {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
FILE* src = fopen(srcPath, "rb");
|
||||
if (!src) return C_ERR_NOTFOUND;
|
||||
|
||||
FILE* dest = fopen(destPath, "wb");
|
||||
if (!dest) {
|
||||
fclose(src);
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
char* buffer = (char*)malloc(COPY_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
c_err_t status = C_ERR_OK;
|
||||
size_t bytes_read;
|
||||
|
||||
// 循环读写块,避免一次性读入大文件撑爆堆内存
|
||||
while ((bytes_read = fread(buffer, 1, COPY_BUFFER_SIZE, src)) > 0) {
|
||||
size_t bytes_written = fwrite(buffer, 1, bytes_read, dest);
|
||||
if (bytes_written < bytes_read) {
|
||||
status = C_ERR_FAIL; // 磁盘空间不足或写入错误
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
|
||||
// 如果中途失败,清理掉生成的不完整目标文件
|
||||
if (status != C_ERR_OK) {
|
||||
remove(destPath);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
c_err_t c_File_Move(const char* oldPath, const char* newPath) {
|
||||
if (!oldPath || oldPath[0] == '\0' || !newPath || newPath[0] == '\0') {
|
||||
return C_ERR_PARAM;
|
||||
}
|
||||
|
||||
// 1. 尝试使用操作系统原生的轻量级重命名/移动
|
||||
if (rename(oldPath, newPath) == 0) {
|
||||
return C_ERR_OK;
|
||||
}
|
||||
|
||||
// 2. 跨分区保底策略:如果因为跨文件系统挂载点导致 rename 失败,则执行 复制 + 删除
|
||||
c_err_t copy_err = c_File_Copy(oldPath, newPath);
|
||||
if (copy_err == C_ERR_OK) {
|
||||
if (remove(oldPath) == 0) {
|
||||
return C_ERR_OK;
|
||||
} else {
|
||||
// 如果删原文件失败,为了数据安全,把新拷过去的文件也撤销,避免数据状态不一致
|
||||
remove(newPath);
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_FAIL;
|
||||
}
|
||||
|
||||
+22
-6
@@ -21,12 +21,6 @@ typedef struct {
|
||||
}c_File_t;
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
@@ -48,6 +42,20 @@ c_err_t c_File_Seek(c_File_t* file, long long position);
|
||||
|
||||
c_err_t c_File_MkDirs(const char* path);
|
||||
|
||||
c_err_t c_File_Rmdir(const char* path);
|
||||
|
||||
c_err_t c_File_Rmdirs(const char* path);
|
||||
|
||||
c_err_t c_File_Delete(const char* fileName);
|
||||
|
||||
/**
|
||||
* @brief 复制指定文件到目标路径(支持大文件流式拷贝)
|
||||
* @param srcPath 源文件路径
|
||||
* @param destPath 目标文件路径
|
||||
* @return 成功返回 C_ERR_OK,失败返回对应错误码
|
||||
*/
|
||||
c_err_t c_File_Copy(const char* srcPath, const char* destPath);
|
||||
|
||||
/**
|
||||
* @brief 检查指定路径的文件或目录是否存在
|
||||
* @param fileName 文件的绝对路径或相对路径
|
||||
@@ -55,4 +63,12 @@ c_err_t c_File_MkDirs(const char* path);
|
||||
*/
|
||||
c_bool_t c_File_IsExist(const char* fileName);
|
||||
|
||||
/**
|
||||
* @brief 移动或重命名文件(支持跨分区移动保底)
|
||||
* @param oldPath 旧文件路径
|
||||
* @param newPath 新文件路径
|
||||
* @return 成功返回 C_ERR_OK,失败返回对应错误码
|
||||
*/
|
||||
c_err_t c_File_Move(const char* oldPath, const char* newPath);
|
||||
|
||||
#endif /*INCLUDED_C_FILE_H*/
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#include <c_float.h>
|
||||
@@ -1,164 +0,0 @@
|
||||
#ifndef INCLUDED_C_FLOAT_H
|
||||
#define INCLUDED_C_FLOAT_H
|
||||
|
||||
#ifndef INCLUDED_FLOAT_H
|
||||
#define INCLUDED_FLOAT_H
|
||||
#include <float.h>
|
||||
#endif /*INCLUDED_FLOAT_H*/
|
||||
|
||||
#ifndef INCLUDED_STDINT_H
|
||||
#define INCLUDED_STDINT_H
|
||||
#include <stdint.h>
|
||||
#endif /*INCLUDED_STDINT_H*/
|
||||
|
||||
#ifndef INCLUDED_MATH_H
|
||||
#define INCLUDED_MATH_H
|
||||
#include <math.h>
|
||||
#endif /*INCLUDED_MATH_H*/
|
||||
|
||||
#ifndef INCLUDED_C_COMPILER_H
|
||||
#include <c_Compiler.h>
|
||||
#endif /*INCLUDED_C_COMPILER_H*/
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
typedef union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
struct {
|
||||
uint32_t sign: 1;
|
||||
uint32_t exponent: 8;
|
||||
uint32_t mantissa: 23;
|
||||
}IEEE754;
|
||||
}c_Float_t;
|
||||
|
||||
typedef union {
|
||||
double d;
|
||||
uint64_t u;
|
||||
struct {
|
||||
uint64_t sign: 1;
|
||||
uint64_t exponent: 11;
|
||||
uint64_t mantissa: 52;
|
||||
};
|
||||
}c_Double_t;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_float_is_eq(const float a, const float b) {
|
||||
return fabsf(a - b) < FLT_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_float_is_gt(const float a, const float b) {
|
||||
return (a - b) > FLT_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_float_is_ge(const float a, const float b) {
|
||||
return (a - b) >= -FLT_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_float_cmp_safe(const float fa, const float fb) {
|
||||
int nan_a = isnan(fa);
|
||||
int nan_b = isnan(fb);
|
||||
|
||||
if (C_UNLIKELY(nan_a || nan_b)) {
|
||||
if (nan_a && nan_b) return 0;
|
||||
if (nan_a) return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fa < fb) return -1;
|
||||
if (fa > fb) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* double */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_double_is_eq(const double a, const double b) {
|
||||
return fabs(a - b) < DBL_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_double_is_gt(const double a, const double b) {
|
||||
return (a - b) > DBL_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_double_is_ge(const double a, const double b) {
|
||||
return (a - b) >= -DBL_EPSILON;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_double_cmp_safe(const double da, const double db) {
|
||||
|
||||
int nan_a = isnan(da);
|
||||
int nan_b = isnan(db);
|
||||
|
||||
/* --- 1. 处理 NaN 的极端分支 --- */
|
||||
/* 采用 C_UNLIKELY 优化,因为大部分待排数据中 NaN 是极少数,提示 CPU 预读正常分支 */
|
||||
if (C_UNLIKELY(nan_a || nan_b)) {
|
||||
if (nan_a && nan_b) return 0; /* 两个都是 NaN,视为相等 */
|
||||
if (nan_a) return 1; /* a 是 NaN,b 是正常数,视为 a > b(NaN排到最后) */
|
||||
return -1; /* a 是正常数,b 是 NaN,视为 a < b */
|
||||
}
|
||||
|
||||
/* --- 2. 正常数值的三向比较分支 --- */
|
||||
if (da < db) return -1;
|
||||
if (da > db) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_float_ptr_cmp_safe(const void* a, const void* b) {
|
||||
float fa = *(const float*)a;
|
||||
float fb = *(const float*)b;
|
||||
|
||||
int nan_a = isnan(fa);
|
||||
int nan_b = isnan(fb);
|
||||
|
||||
if (C_UNLIKELY(nan_a || nan_b)) {
|
||||
if (nan_a && nan_b) return 0;
|
||||
if (nan_a) return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fa < fb) return -1;
|
||||
if (fa > fb) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
int c_double_ptr_cmp_safe(const void* a, const void* b) {
|
||||
double da = *(const double*)a;
|
||||
double db = *(const double*)b;
|
||||
|
||||
int nan_a = isnan(da);
|
||||
int nan_b = isnan(db);
|
||||
|
||||
/* --- 1. 处理 NaN 的极端分支 --- */
|
||||
/* 采用 C_UNLIKELY 优化,因为大部分待排数据中 NaN 是极少数,提示 CPU 预读正常分支 */
|
||||
if (C_UNLIKELY(nan_a || nan_b)) {
|
||||
if (nan_a && nan_b) return 0; /* 两个都是 NaN,视为相等 */
|
||||
if (nan_a) return 1; /* a 是 NaN,b 是正常数,视为 a > b(NaN排到最后) */
|
||||
return -1; /* a 是正常数,b 是 NaN,视为 a < b */
|
||||
}
|
||||
|
||||
/* --- 2. 正常数值的三向比较分支 --- */
|
||||
if (da < db) return -1;
|
||||
if (da > db) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /*INCLUDED_C_FLOAT_H*/
|
||||
+372
-14
@@ -1,23 +1,381 @@
|
||||
#include "c_float.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <c_Test.h>
|
||||
|
||||
static void c_Double_print(c_Double_t value) {
|
||||
uint64_t sign = value.sign;
|
||||
uint64_t exponent = value.exponent;
|
||||
uint64_t fraction = value.mantissa;
|
||||
printf("数值: %.15f\n", value.d);
|
||||
printf("整体十六进制: 0x%016llX\n", (unsigned long long)value.u);
|
||||
printf("符号位 (Sign): %llu (%s)\n", (unsigned long long)sign, sign ? "负" : "正");
|
||||
printf("指数位 (Exponent 原始值): %llu (实际 2 阶: %d)\n",
|
||||
(unsigned long long)exponent, (int)exponent - 1023);
|
||||
printf("尾数位 (Fraction 16进制): 0x%013llX\n", (unsigned long long)fraction);
|
||||
printf("---------------------------------------\n");
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/**
|
||||
* @brief 辅助工具:将 C 语言原生双精度 double 转换为 64 位原始位码 (uint64_t)
|
||||
*/
|
||||
static uint64_t to_raw64(double d) {
|
||||
c_Double_t u;
|
||||
u.d = d;
|
||||
return u.raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 辅助工具:将 64 位原始位码 (uint64_t) 还原为 C 语言原生双精度 double
|
||||
*/
|
||||
static double to_double(uint64_t raw) {
|
||||
c_Double_t u;
|
||||
u.raw = raw;
|
||||
return u.d;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
// 用例 1:测试 Float 分类状态识别函数(IsZero, IsInf, IsNAN 等基本位打包判定)
|
||||
void test_float_classification_and_pack() {
|
||||
c_Float_t val;
|
||||
|
||||
// 1. 测试 Pack 是否能准确组装成标准浮点位
|
||||
// 符号=0, 指数=127(偏移后为0), 尾数=0 -> 应该代表 1.0f
|
||||
uint32_t packed = c_Float_Pack(0, 127, 0);
|
||||
val.raw = packed;
|
||||
ASSERT_MSG(val.f == 1.0f, "c_Float_Pack failed to assemble 1.0f");
|
||||
|
||||
// 2. 测试 正负零 (0.0f 和 -0.0f)
|
||||
val.f = 0.0f;
|
||||
ASSERT_MSG(c_Float_IsZero(val.raw) == true, "0.0f should be identified as zero");
|
||||
val.f = -0.0f;
|
||||
ASSERT_MSG(c_Float_IsZero(val.raw) == true, "-0.0f should be identified as zero");
|
||||
|
||||
// 3. 测试 正负无穷大 (Inf)
|
||||
val.raw = C_FLOAT_POS_INF;
|
||||
ASSERT_MSG(c_Float_IsInf(val.raw) == true, "POS_INF must be Inf");
|
||||
ASSERT_MSG(c_Float_IsPosInf(val.raw) == true, "POS_INF must be PosInf");
|
||||
|
||||
val.raw = C_FLOAT_NEG_INF;
|
||||
ASSERT_MSG(c_Float_IsInf(val.raw) == true, "NEG_INF must be Inf");
|
||||
ASSERT_MSG(c_Float_IsNegInf(val.raw) == true, "NEG_INF must be NegInf");
|
||||
|
||||
// 4. 测试 NaN(指数全为1,尾数不为0)
|
||||
val.raw = C_FLOAT_EXP_MASK | 0x00000001U; // 制造一个 NaN
|
||||
ASSERT_MSG(c_Float_IsNAN(val.raw) == 1, "Should be recognized as NaN");
|
||||
|
||||
val.raw = C_FLOAT_POS_INF; // 无穷大的尾数是0,不属于 NaN
|
||||
ASSERT_MSG(c_Float_IsNAN(val.raw) == 0, "Infinity is NOT NaN");
|
||||
}
|
||||
|
||||
|
||||
// 用例 2:测试 Float 基础数学四则运算(数值运算准确性)
|
||||
void test_float_math_operations() {
|
||||
c_Float_t res, out_add, out_sub, out_mul, out_div;
|
||||
c_Float_t a, b;
|
||||
|
||||
a.f = 5.5f;
|
||||
b.f = 2.25f;
|
||||
|
||||
// 1. 加法测试 5.5 + 2.25 = 7.75
|
||||
out_add.raw = c_Float_Add(a.raw, b.raw);
|
||||
res.f = 7.75f;
|
||||
ASSERT_INT_EQ_MSG(res.raw, out_add.raw, "Soft-Float Add failed (5.5 + 2.25)");
|
||||
|
||||
// 2. 减法测试 5.5 - 2.25 = 3.25
|
||||
out_sub.raw = c_Float_Sub(a.raw, b.raw);
|
||||
res.f = 3.25f;
|
||||
ASSERT_INT_EQ_MSG(res.raw, out_sub.raw, "Soft-Float Sub failed (5.5 - 2.25)");
|
||||
|
||||
// 3. 乘法测试 5.5 * 2.25 = 12.375
|
||||
out_mul.raw = c_Float_Mul(a.raw, b.raw);
|
||||
res.f = 12.375f;
|
||||
ASSERT_INT_EQ_MSG(res.raw, out_mul.raw, "Soft-Float Mul failed (5.5 * 2.25)");
|
||||
|
||||
// 4. 除法测试 5.5 / 2.25 = 2.444444... (通过联合体转换进行交叉对比)
|
||||
out_div.raw = c_Float_Div(a.raw, b.raw);
|
||||
float expected_div = 5.5f / 2.25f;
|
||||
uint32_t expected_raw = ((c_Float_t){.f = expected_div}).raw;
|
||||
|
||||
// 经过软除法精度升级后,这里预期可以做到每一个二进制位都完全绝对对齐(0 ULP 误差)
|
||||
ASSERT_INT_EQ_MSG((int)expected_raw, (int)out_div.raw, "Soft-Float Div Round-to-Nearest-Even failed to align with hardware bits");
|
||||
}
|
||||
|
||||
void test_float_div_complete() {
|
||||
c_Float_t a, b, out;
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 1:常规数值除法 (15.5 / 2.0 = 7.75)
|
||||
// -------------------------------------------------------------
|
||||
a.f = 15.5f;
|
||||
b.f = 2.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(fabsf(7.75f - out.f)<FLT_EPSILON, "Regular division failed (15.5 / 2.0)");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 2:符号位正确结合测试 (-15.5 / 2.0 = -7.75)
|
||||
// -------------------------------------------------------------
|
||||
a.f = -15.5f;
|
||||
b.f = 2.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(fabsf(-7.75f - out.f) < FLT_EPSILON, "Signed division failed (-15.5 / 2.0)");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 3:有限数除以 0.0 —— 预期触发 ±Inf 拦截 (X / 0 = Inf)
|
||||
// -------------------------------------------------------------
|
||||
a.f = 5.25f;
|
||||
b.f = 0.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsPosInf(out.raw), "5.25 / 0.0 must yield Positive Infinity");
|
||||
|
||||
a.f = -5.25f;
|
||||
b.f = 0.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsNegInf(out.raw), "-5.25 / 0.0 must yield Negative Infinity");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 4:零除以零 边界熔断 —— 预期触发 NaN (0.0 / 0.0 = NaN)
|
||||
// -------------------------------------------------------------
|
||||
a.f = 0.0f;
|
||||
b.f = 0.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw), "0.0 / 0.0 must result in NaN");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 5:无穷大除以无穷大 边界熔断 —— 预期触发 NaN (Inf / Inf = NaN)
|
||||
// -------------------------------------------------------------
|
||||
out.raw = c_Float_Div(C_FLOAT_POS_INF, C_FLOAT_POS_INF);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw), "PosInf / PosInf must result in NaN");
|
||||
|
||||
out.raw = c_Float_Div(C_FLOAT_POS_INF, C_FLOAT_NEG_INF);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw), "PosInf / NegInf must result in NaN");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 测试 6:精确偶数舍入测试(循环除法产生除不尽的无限循环小数)
|
||||
// -------------------------------------------------------------
|
||||
a.f = 1.0f;
|
||||
b.f = 3.0f; // 1.0 / 3.0 = 0.33333333...
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
|
||||
// 提取系统原生结果进行高精密比对(ULP 误差必须为 0)
|
||||
float native_expected = 1.0f / 3.0f;
|
||||
c_Float_t native_val = {.f = native_expected};
|
||||
ASSERT_INT_EQ_MSG((int)native_val.raw, (int)out.raw, "1.0 / 3.0 Round-to-Nearest-Even failed");
|
||||
}
|
||||
|
||||
|
||||
// 用例 3:测试 Float IEEE 754 软加法器/乘法器的特殊边界极限(这最容易导致软浮点数崩溃或死循环)
|
||||
void test_float_edge_cases() {
|
||||
c_Float_t a, b, out;
|
||||
|
||||
// 1. 任何数与 NaN 运算都必须得到 NaN
|
||||
a.raw = C_FLOAT_EXP_MASK | 0x1234U; // NaN
|
||||
b.f = 5.0f;
|
||||
out.raw = c_Float_Add(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw) == 1, "NaN + Number must result in NaN");
|
||||
|
||||
// 2. 边界:正无穷大 + 负无穷大 应该得到 NaN (Inf - Inf = NaN)
|
||||
out.raw = c_Float_Add(C_FLOAT_POS_INF, C_FLOAT_NEG_INF);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw), "PosInf + NegInf must result in NaN");
|
||||
|
||||
// 3. 边界:有限非零数除以 0 应该触发除零异常得到 无穷大 (X / 0 = Inf)
|
||||
a.f = 3.5f;
|
||||
b.f = 0.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsPosInf(out.raw), "3.5 / 0.0 must result in Positive Infinity");
|
||||
|
||||
// 4. 边界:0 / 0 应该产生 NaN
|
||||
a.f = 0.0f;
|
||||
b.f = 0.0f;
|
||||
out.raw = c_Float_Div(a.raw, b.raw);
|
||||
ASSERT_MSG(c_Float_IsNAN(out.raw) == 1, "0.0 / 0.0 must result in NaN");
|
||||
}
|
||||
|
||||
|
||||
// 用例 4:测试 Double 的数值运算及大小比较行为 (c_Double_Cmp)
|
||||
void test_double_operations_and_compare() {
|
||||
c_Double_t a, b, out;
|
||||
|
||||
// 1. 测试基础双精度加法
|
||||
a.d = 123456789.12345;
|
||||
b.d = 987654321.98765;
|
||||
out.raw = c_Double_Add(a.raw, b.raw);
|
||||
|
||||
c_Double_t expected;
|
||||
expected.d = a.d + b.d;
|
||||
int64_t diff = (int64_t)out.raw - (int64_t)expected.raw;
|
||||
ASSERT_MSG(labs(diff) <= 1, "c_Double_Add precision verification mismatched");
|
||||
|
||||
// 2. 测试 c_Double_Cmp 逻辑
|
||||
// 规定返回值规范:a < b 返回负数,a == b 返回 0,a > b 返回正数
|
||||
a.d = 100.5;
|
||||
b.d = 200.5;
|
||||
ASSERT_MSG(c_Double_Cmp(a.raw, b.raw) < 0, "100.5 should be less than 200.5");
|
||||
ASSERT_MSG(c_Double_Cmp(b.raw, a.raw) > 0, "200.5 should be greater than 100.5");
|
||||
ASSERT_MSG(c_Double_Cmp(a.raw, a.raw) == 0, "100.5 should be equal to itself");
|
||||
|
||||
// 3. 原生内联函数的包装测试 (c_double_cmp)
|
||||
ASSERT_MSG(c_double_cmp(10.0, 20.0) < 0, "Inline double compare wrapper failed");
|
||||
}
|
||||
|
||||
void test_float_isnan_pure_bits() {
|
||||
// 场景 1:制造标准常规数值(如 1.0f)—— 预期:非 NaN (0)
|
||||
// 符号=0, 指数=127, 尾数=0
|
||||
uint32_t normal_num = c_Float_Pack(0, 127, 0);
|
||||
ASSERT_INT_EQ_MSG(0, c_Float_IsNAN(normal_num), "Normal number 1.0f must NOT be NaN");
|
||||
|
||||
// 场景 2:正无穷大 (C_FLOAT_POS_INF) —— 预期:非 NaN (0)
|
||||
// 它的指数全为 1,但尾数严格为 0
|
||||
ASSERT_INT_EQ_MSG(0, c_Float_IsNAN(C_FLOAT_POS_INF), "Positive Infinity must NOT be NaN");
|
||||
ASSERT_INT_EQ_MSG(0, c_Float_IsNAN(C_FLOAT_NEG_INF), "Negative Infinity must NOT be NaN");
|
||||
|
||||
// 场景 3:制造一个最微小的 Quiet NaN (QNaN) —— 预期:是 NaN (1)
|
||||
// 指数全为 1 (0xFF),尾数最高位为 1 (0x400000)
|
||||
uint32_t qnan_bits = c_Float_Pack(0, 0xFF, 0x400000U);
|
||||
ASSERT_MSG(c_Float_IsNAN(qnan_bits), "Quiet NaN bits must be recognized as NaN");
|
||||
|
||||
// 场景 4:制造一个最微小的 Signaling NaN (SNaN) —— 预期:是 NaN (1)
|
||||
// 指数全为 1 (0xFF),尾数最低位为 1 (0x000001)
|
||||
uint32_t snan_bits = c_Float_Pack(0, 0xFF, 0x000001U);
|
||||
ASSERT_MSG(c_Float_IsNAN(snan_bits), "Signaling NaN bits must be recognized as NaN");
|
||||
|
||||
// 场景 5:测试带有符号位的 NaN (负 NaN) —— 预期:是 NaN (1)
|
||||
// IEEE 754 规范中,NaN 的符号位不影响它是 NaN 的事实
|
||||
uint32_t neg_nan_bits = c_Float_Pack(1, 0xFF, 0x7FFFFFU);
|
||||
ASSERT_MSG(c_Float_IsNAN(neg_nan_bits), "Negative NaN bits must also be recognized as NaN");
|
||||
}
|
||||
|
||||
void test_float_inf_plus_neginf() {
|
||||
// 1. 获取正无穷大与负无穷大的位表示
|
||||
uint32_t pos_inf = C_FLOAT_POS_INF; // 0x7F800000
|
||||
uint32_t neg_inf = C_FLOAT_NEG_INF; // 0xFF800000
|
||||
|
||||
// 2. 执行待测的软浮点加法:(+Inf) + (-Inf)
|
||||
uint32_t result_raw = c_Float_Add(pos_inf, neg_inf);
|
||||
|
||||
// 3. 核心断言:结果必须是 NaN
|
||||
// 使用 c_Float_IsNAN 验证其特征是否为:指数全 1,尾数非 0
|
||||
ASSERT_MSG(c_Float_IsNAN(result_raw), "IEEE 754 standard: (+Inf) + (-Inf) must produce NaN");
|
||||
|
||||
// 4. 反向验证:它绝对不能再被误判为任何形式的无穷大或零
|
||||
ASSERT_MSG(!c_Float_IsInf(result_raw), "Result NaN must not be classified as Infinity");
|
||||
ASSERT_MSG(!c_Float_IsZero(result_raw), "Result NaN must not be classified as Zero");
|
||||
|
||||
// 5. 跨双精度对称验证:(+Inf) + (-Inf) 同样适用于 64 位双精度
|
||||
uint64_t d_pos_inf = C_DOUBLE_POS_INF;
|
||||
uint64_t d_neg_inf = C_DOUBLE_NEG_INF;
|
||||
uint64_t d_result_raw = c_Double_Add(d_pos_inf, d_neg_inf);
|
||||
|
||||
ASSERT_MSG(c_Double_IsNAN(d_result_raw), "IEEE 754 standard: Double (+Inf) + (-Inf) must produce NaN");
|
||||
}
|
||||
|
||||
void test_double_mul_and_div_complete() {
|
||||
c_Double_t da, db, dout;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 1:验证跨 64 位大整数相乘的精确偶数舍入
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 1.23456789012345;
|
||||
db.d = -9.87654321098765;
|
||||
dout.raw = c_Double_Mul(da.raw, db.raw);
|
||||
|
||||
double expected_mul = 1.23456789012345 * -9.87654321098765;
|
||||
c_Double_t native_mul = {.d = expected_mul};
|
||||
|
||||
// 【终极断言升级】:杜绝 int 转换截断,对双精度 64 位全局原始编码进行无差错硬核对齐
|
||||
ASSERT_MSG(native_mul.raw == dout.raw,
|
||||
"c_Double_Mul 64-bit full-width precision failed to align with hardware FPU");
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 2:验证双精度无限循环小数除法的长窗口状态机精度
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 1.0;
|
||||
db.d = 3.0; // 1.0 / 3.0
|
||||
dout.raw = c_Double_Div(da.raw, db.raw);
|
||||
|
||||
double expected_div = 1.0 / 3.0;
|
||||
c_Double_t native_div = {.d = expected_div};
|
||||
ASSERT_INT_EQ_MSG((int)native_div.parts.fraction, (int)dout.parts.fraction, "c_Double_Div bit-level precision error at 1.0/3.0");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 3:验证双精度 0.0 与 无穷大的复合熔断边界
|
||||
// -----------------------------------------------------------------
|
||||
// 边界 A:0.0 * Inf -> 必须返回 NaN
|
||||
uint64_t zero_mul_inf = c_Double_Mul(to_raw64(0.0), C_DOUBLE_POS_INF);
|
||||
ASSERT_MSG(c_Double_IsNAN(zero_mul_inf), "Double 0.0 * +Inf must result in NaN");
|
||||
|
||||
// 边界 B:0.0 / 0.0 -> 必须返回 NaN
|
||||
uint64_t zero_div_zero = c_Double_Div(to_raw64(0.0), to_raw64(-0.0));
|
||||
ASSERT_MSG(c_Double_IsNAN(zero_div_zero), "Double 0.0 / -0.0 must result in NaN");
|
||||
|
||||
// 边界 C:常规有限双精度数除以 0.0 -> 产生无穷大
|
||||
da.d = -55.5;
|
||||
uint64_t div_zero = c_Double_Div(to_raw64(da.d), to_raw64(0.0));
|
||||
ASSERT_MSG(c_Double_IsNegInf(div_zero), "Negative double divided by 0.0 must result in -Inf");
|
||||
}
|
||||
|
||||
void test_double_add_complete() {
|
||||
c_Double_t da, db, dout;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 1:常规双精度数值加减(50.25 + 25.5 = 75.75)
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 50.25;
|
||||
db.d = 25.5;
|
||||
dout.raw = c_Double_Add(da.raw, db.raw);
|
||||
|
||||
// 使用真值进行精度验证
|
||||
ASSERT_MSG(fabs(dout.d - 75.75) < 1e-9, "Regular Double Add numerical verification failed");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 2:验证正负无穷大冲抵边界熔断(+Inf + -Inf = NaN)
|
||||
// -----------------------------------------------------------------
|
||||
uint64_t res_nan = c_Double_Add(C_DOUBLE_POS_INF, C_DOUBLE_NEG_INF);
|
||||
ASSERT_MSG(c_Double_IsNAN(res_nan), "IEEE 754: Double (+Inf) + (-Inf) must produce NaN");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 3:验证异号数值完全抵消(5.5 + -5.5 = +0.0)
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 5.5;
|
||||
db.d = -5.5;
|
||||
dout.raw = c_Double_Add(da.raw, db.raw);
|
||||
// 验证返回的是否是干净的、符号位为0的正零 (0x0000000000000000)
|
||||
ASSERT_INT_EQ_MSG(0, (int)dout.raw, "Opposite numbers sum must strictly result in +0.0 bits");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 4:大跨度对阶精度测试(1.0 + 1e-17 触发全移出边界)
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 1.0;
|
||||
db.d = 1e-17; // 这个值太小了,在双精度 53 位尾数对阶时会被完全移出去,但会触发 sticky 位置 1
|
||||
dout.raw = c_Double_Add(da.raw, db.raw);
|
||||
// 按照偶数舍入规则,sticky=1,GRS=001 <= 4,将被舍去,结果应该严格保持为 1.0
|
||||
ASSERT_MSG(dout.d == 1.0, "Large exponent gap shift processing failed");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 测试 5:向最近偶数舍入的 0 ULP 硬件级绝对对齐校验
|
||||
// -----------------------------------------------------------------
|
||||
da.d = 1.23456789012345;
|
||||
db.d = 9.87654321098765;
|
||||
dout.raw = c_Double_Add(da.raw, db.raw);
|
||||
|
||||
double native_expected = 1.23456789012345 + 9.87654321098765;
|
||||
c_Double_t native_val = {.d = native_expected};
|
||||
|
||||
// 通过对比尾数域,验证是否做到了 100% 硬件位对齐
|
||||
ASSERT_INT_EQ_MSG((int)native_val.parts.fraction, (int)dout.parts.fraction,
|
||||
"Soft-Double Add failed to align with hardware FPU bits");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
c_Double_t d={.d=1.0f};
|
||||
c_Double_print(d);
|
||||
TEST_START(Starting Unit Tests);
|
||||
|
||||
return 0;
|
||||
// 运行需要内存环境的用例
|
||||
RUN_TEST(test_float_classification_and_pack);
|
||||
RUN_TEST(test_float_math_operations);
|
||||
RUN_TEST(test_float_edge_cases);
|
||||
RUN_TEST(test_double_operations_and_compare);
|
||||
RUN_TEST(test_float_isnan_pure_bits);
|
||||
RUN_TEST(test_float_inf_plus_neginf);
|
||||
RUN_TEST(test_float_div_complete);
|
||||
RUN_TEST(test_double_mul_and_div_complete);
|
||||
RUN_TEST(test_double_add_complete);
|
||||
|
||||
|
||||
// 打印最终统计报告
|
||||
TEST_REPORT();
|
||||
|
||||
RETURN_TEST_STATUS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user