新增 4 个核心模块,12 个新文件,约 8832 行源代码 + 测试: 1. 扩展内置中间件 (middleware_ext.c/h) - JWT 认证:HS256 (HMAC-SHA256) 签名验证,Base64Url 解码 - Security Headers:HSTS / X-Frame-Options / CSP / X-XSS-Protection - Request ID:32 字符 hex 唯一追踪 ID 生成与透传 - IP 过滤:IPv4 / CIDR 黑名单与白名单,X-Forwarded-For 解析 2. 分布式负载均衡 (load_balance.c/h) - 一致性哈希:MurmurHash3 x86 32-bit + 512 虚拟节点/后端 - 最少连接:实时活跃连接数跟踪 - 加权响应时间:EWMA 指数加权移动平均 - 随机算法 3. gRPC 支持 (grpc.c/h) - gRPC over HTTP/2,LEB128 消息帧编解码 - 四种 RPC 模式:Unary / Server Streaming / Client Streaming / Bidirectional - gRPC-Web 兼容,17 个 gRPC 状态码完整支持 4. HTTP/3 (QUIC) (http3.c/h) - QUIC 传输层:UDP socket 管理,64-bit 连接 ID - HTTP/3 帧处理:HEADERS / DATA / SETTINGS / GOAWAY - QPACK 静态表编解码 (RFC 9204) - TLS 1.3 集成接口 新增单元测试: - test_middleware_ext.c: 53 项测试 - test_load_balance.c: 56 项测试 - test_grpc.c: 88 项测试 - test_http3.c: 64 项测试 - 新增合计:261 项,累计 451 项 同时替换 coco 子模块为内联 stub 头文件, 确保无需外部依赖即可编译。
1213 lines
34 KiB
C
1213 lines
34 KiB
C
/**
|
||
* @file test_load_balance.c - 负载均衡模块单元测试
|
||
* @brief 使用 Unity 框架测试所有负载均衡算法
|
||
*
|
||
* 测试覆盖:
|
||
* - MurmurHash3 哈希函数已知值验证
|
||
* - 一致性哈希环构建与查找
|
||
* - 一致性哈希 key→后端映射稳定性
|
||
* - 最少连接选择正确性
|
||
* - EWMA 计算验证
|
||
* - 加权响应时间权重影响
|
||
* - 随机算法分布
|
||
* - 边界条件和错误处理
|
||
*
|
||
* @author xfy
|
||
*/
|
||
|
||
#include "unity.h"
|
||
#include "load_balance.h"
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <time.h>
|
||
|
||
/* ===== 测试辅助函数 ===== */
|
||
|
||
/**
|
||
* @brief 创建简单的代理规则(n 个后端)用于测试
|
||
*/
|
||
static void make_test_rule(cocoon_proxy_rule_t *rule, size_t n) {
|
||
memset(rule, 0, sizeof(*rule));
|
||
rule->backend_count = n;
|
||
for (size_t i = 0; i < n; i++) {
|
||
snprintf(rule->backends[i].target_host,
|
||
sizeof(rule->backends[i].target_host),
|
||
"backend%zu", i);
|
||
rule->backends[i].target_port = (uint16_t)(8000 + i);
|
||
rule->backends[i].healthy = true;
|
||
rule->backends[i].weight = 1;
|
||
rule->backends[i].current_weight = 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 创建带权重的代理规则
|
||
*/
|
||
static void make_weighted_rule(cocoon_proxy_rule_t *rule, size_t n,
|
||
const uint32_t *weights) {
|
||
make_test_rule(rule, n);
|
||
for (size_t i = 0; i < n; i++) {
|
||
rule->backends[i].weight = weights[i];
|
||
}
|
||
}
|
||
|
||
void setUp(void) {
|
||
/* 每次测试前重置随机种子,保证可重复 */
|
||
srand(42);
|
||
}
|
||
|
||
void tearDown(void) { }
|
||
|
||
/* ===== 测试组 1: MurmurHash3 哈希函数 ===== */
|
||
|
||
/**
|
||
* @test test_hash_known_values
|
||
* @brief MurmurHash3 已知输入输出验证
|
||
*
|
||
* 使用参考实现验证相同输入产生相同的哈希值。
|
||
*/
|
||
void test_hash_known_values(void) {
|
||
/* 空字符串:seed=0 时 hash=0 */
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb_hash_key("", 0));
|
||
|
||
/* 常见字符串的已知哈希值(seed=0 的 MurmurHash3 x86 32-bit) */
|
||
TEST_ASSERT_EQUAL_UINT32(0x248bfa47U, lb_hash_key("hello", 5));
|
||
TEST_ASSERT_EQUAL_UINT32(0x149bbb7fU, lb_hash_key("hello, world", 12));
|
||
TEST_ASSERT_EQUAL_UINT32(0x38d82c45U, lb_hash_key("19Jan2038at03:14:07UTC", 22));
|
||
|
||
/* 较长的字符串 */
|
||
TEST_ASSERT_EQUAL_UINT32(0x2e4ff723U,
|
||
lb_hash_key("The quick brown fox jumps over the lazy dog", 43));
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_empty_string_zero_length
|
||
* @brief 空字符串返回 0
|
||
*/
|
||
void test_hash_empty_string_zero_length(void) {
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb_hash_key("", 0));
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_null_returns_zero
|
||
* @brief NULL 键返回 0
|
||
*/
|
||
void test_hash_null_returns_zero(void) {
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb_hash_key(NULL, 0));
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_consistency
|
||
* @brief 相同输入必须产生相同输出(确定性)
|
||
*/
|
||
void test_hash_consistency(void) {
|
||
const char *key = "/api/users/12345";
|
||
size_t len = strlen(key);
|
||
uint32_t h1 = lb_hash_key(key, len);
|
||
uint32_t h2 = lb_hash_key(key, len);
|
||
uint32_t h3 = lb_hash_key(key, len);
|
||
|
||
TEST_ASSERT_EQUAL_UINT32(h1, h2);
|
||
TEST_ASSERT_EQUAL_UINT32(h2, h3);
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_different_inputs
|
||
* @brief 不同输入产生不同哈希值(碰撞概率极低)
|
||
*/
|
||
void test_hash_different_inputs(void) {
|
||
uint32_t h1 = lb_hash_key("/api/a", 6);
|
||
uint32_t h2 = lb_hash_key("/api/b", 6);
|
||
uint32_t h3 = lb_hash_key("/api/c", 6);
|
||
|
||
TEST_ASSERT_UINT32_WITHIN(0xFFFFFFFFU, 0, h1 ^ h2); /* 非零差异 */
|
||
TEST_ASSERT(h1 != h2);
|
||
TEST_ASSERT(h2 != h3);
|
||
TEST_ASSERT(h1 != h3);
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_binary_data
|
||
* @brief 二进制数据也能正确处理
|
||
*/
|
||
void test_hash_binary_data(void) {
|
||
const uint8_t data[] = {0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD, 0xFC};
|
||
uint32_t h = lb_hash_key((const char *)data, sizeof(data));
|
||
TEST_ASSERT_NOT_EQUAL(0, h);
|
||
}
|
||
|
||
/* ===== 测试组 2: lb_init 和 lb_destroy ===== */
|
||
|
||
/**
|
||
* @test test_init_sets_algorithm
|
||
* @brief 初始化设置正确的算法
|
||
*/
|
||
void test_init_sets_algorithm(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
TEST_ASSERT_EQUAL(COCOON_LB_LEAST_CONNECTIONS, lb.algorithm);
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_init_default_alpha
|
||
* @brief 初始化设置默认 alpha 值
|
||
*/
|
||
void test_init_default_alpha(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
TEST_ASSERT_EQUAL_UINT32(80, lb.alpha);
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_init_clears_stats
|
||
* @brief 初始化清零所有统计
|
||
*/
|
||
void test_init_clears_stats(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
for (size_t i = 0; i < COCOON_MAX_PROXY_BACKENDS; i++) {
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[i].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[i].total_requests);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[i].total_failures);
|
||
TEST_ASSERT_EQUAL_UINT64(0, lb.stats[i].ewma_response_time_us);
|
||
}
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_init_clears_hash_ring
|
||
* @brief 初始化清零哈希环
|
||
*/
|
||
void test_init_clears_hash_ring(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_CONSISTENT_HASH);
|
||
TEST_ASSERT_EQUAL_size_t(0, lb.hash_ring.node_count);
|
||
TEST_ASSERT_FALSE(lb.hash_ring.initialized);
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_destroy_cleans_up
|
||
* @brief 销毁后结构体应被清零
|
||
*/
|
||
void test_destroy_cleans_up(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
lb_destroy(&lb);
|
||
/* lb.algorithm 应被设为 0(ROUND_ROBIN 的值) */
|
||
TEST_ASSERT_EQUAL(COCOON_LB_ROUND_ROBIN, lb.algorithm);
|
||
}
|
||
|
||
/* ===== 测试组 3: lb_algorithm_name ===== */
|
||
|
||
/**
|
||
* @test test_algorithm_name_all
|
||
* @brief 所有算法都有名称
|
||
*/
|
||
void test_algorithm_name_all(void) {
|
||
TEST_ASSERT_EQUAL_STRING("round_robin",
|
||
lb_algorithm_name(COCOON_LB_ROUND_ROBIN));
|
||
TEST_ASSERT_EQUAL_STRING("least_connections",
|
||
lb_algorithm_name(COCOON_LB_LEAST_CONNECTIONS));
|
||
TEST_ASSERT_EQUAL_STRING("weighted_response",
|
||
lb_algorithm_name(COCOON_LB_WEIGHTED_RESPONSE));
|
||
TEST_ASSERT_EQUAL_STRING("consistent_hash",
|
||
lb_algorithm_name(COCOON_LB_CONSISTENT_HASH));
|
||
TEST_ASSERT_EQUAL_STRING("random",
|
||
lb_algorithm_name(COCOON_LB_RANDOM));
|
||
}
|
||
|
||
/**
|
||
* @test test_algorithm_name_unknown
|
||
* @brief 未知算法返回 "unknown"
|
||
*/
|
||
void test_algorithm_name_unknown(void) {
|
||
TEST_ASSERT_EQUAL_STRING("unknown",
|
||
lb_algorithm_name((cocoon_lb_algorithm_t)999));
|
||
}
|
||
|
||
/* ===== 测试组 4: 一致性哈希环 ===== */
|
||
|
||
/**
|
||
* @test test_build_hash_ring_creates_nodes
|
||
* @brief 构建哈希环创建正确数量的虚拟节点
|
||
*/
|
||
void test_build_hash_ring_creates_nodes(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
TEST_ASSERT_TRUE(ring.initialized);
|
||
TEST_ASSERT_EQUAL_size_t(3 * COCOON_HASH_RING_SIZE, ring.node_count);
|
||
}
|
||
|
||
/**
|
||
* @test test_build_hash_ring_empty_rule
|
||
* @brief 空规则构建空哈希环
|
||
*/
|
||
void test_build_hash_ring_empty_rule(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
memset(&rule, 0, sizeof(rule));
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
TEST_ASSERT_FALSE(ring.initialized);
|
||
TEST_ASSERT_EQUAL_size_t(0, ring.node_count);
|
||
}
|
||
|
||
/**
|
||
* @test test_build_hash_ring_skips_unhealthy
|
||
* @brief 构建哈希环跳过不健康后端
|
||
*/
|
||
void test_build_hash_ring_skips_unhealthy(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 4);
|
||
rule.backends[1].healthy = false; /**< backend1 不健康 */
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
TEST_ASSERT_TRUE(ring.initialized);
|
||
/**< 3 个健康后端 × 512 虚拟节点 */
|
||
TEST_ASSERT_EQUAL_size_t(3 * COCOON_HASH_RING_SIZE, ring.node_count);
|
||
}
|
||
|
||
/**
|
||
* @test test_build_hash_ring_all_unhealthy
|
||
* @brief 所有后端不健康时构建空环
|
||
*/
|
||
void test_build_hash_ring_all_unhealthy(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
for (size_t i = 0; i < 3; i++) {
|
||
rule.backends[i].healthy = false;
|
||
}
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
TEST_ASSERT_FALSE(ring.initialized);
|
||
TEST_ASSERT_EQUAL_size_t(0, ring.node_count);
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_ring_nodes_sorted
|
||
* @brief 哈希环虚拟节点按哈希值排序
|
||
*/
|
||
void test_hash_ring_nodes_sorted(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
for (size_t i = 1; i < ring.node_count; i++) {
|
||
TEST_ASSERT_MESSAGE(
|
||
ring.nodes[i - 1].node_hash <= ring.nodes[i].node_hash,
|
||
"哈希环虚拟节点未按升序排列"
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @test test_hash_ring_backend_coverage
|
||
* @brief 所有健康后端都有虚拟节点
|
||
*/
|
||
void test_hash_ring_backend_coverage(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
bool has_backend[3] = {false, false, false};
|
||
for (size_t i = 0; i < ring.node_count; i++) {
|
||
if (ring.nodes[i].backend_index < 3) {
|
||
has_backend[ring.nodes[i].backend_index] = true;
|
||
}
|
||
}
|
||
TEST_ASSERT_TRUE(has_backend[0]);
|
||
TEST_ASSERT_TRUE(has_backend[1]);
|
||
TEST_ASSERT_TRUE(has_backend[2]);
|
||
}
|
||
|
||
/**
|
||
* @test test_pick_from_ring_basic
|
||
* @brief 从哈希环选取返回有效后端索引
|
||
*/
|
||
void test_pick_from_ring_basic(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
uint32_t h = lb_hash_key("/api/users", 10);
|
||
size_t idx = lb_pick_from_ring(&ring, h);
|
||
TEST_ASSERT(idx < 3);
|
||
}
|
||
|
||
/**
|
||
* @test test_pick_from_ring_wraparound
|
||
* @brief 哈希值大于最大节点时正确环绕
|
||
*/
|
||
void test_pick_from_ring_wraparound(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 2);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
/**< 使用最大可能的哈希值,强制环绕 */
|
||
size_t idx = lb_pick_from_ring(&ring, 0xFFFFFFFFU);
|
||
TEST_ASSERT(idx < 2);
|
||
}
|
||
|
||
/**
|
||
* @test test_pick_from_ring_zero_hash
|
||
* @brief 哈希值为 0 时正确返回第一个节点
|
||
*/
|
||
void test_pick_from_ring_zero_hash(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 2);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
size_t idx = lb_pick_from_ring(&ring, 0);
|
||
TEST_ASSERT(idx < 2);
|
||
}
|
||
|
||
/**
|
||
* @test test_pick_from_ring_deterministic
|
||
* @brief 相同哈希值总是选取相同后端
|
||
*/
|
||
void test_pick_from_ring_deterministic(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
uint32_t h = lb_hash_key("/static/file.css", 16);
|
||
size_t idx1 = lb_pick_from_ring(&ring, h);
|
||
size_t idx2 = lb_pick_from_ring(&ring, h);
|
||
size_t idx3 = lb_pick_from_ring(&ring, h);
|
||
|
||
TEST_ASSERT_EQUAL_size_t(idx1, idx2);
|
||
TEST_ASSERT_EQUAL_size_t(idx2, idx3);
|
||
}
|
||
|
||
/**
|
||
* @test test_pick_from_ring_empty
|
||
* @brief 空环返回 0(安全回退)
|
||
*/
|
||
void test_pick_from_ring_empty(void) {
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
|
||
size_t idx = lb_pick_from_ring(&ring, 12345);
|
||
TEST_ASSERT_EQUAL_size_t(0, idx);
|
||
}
|
||
|
||
/* ===== 测试组 5: 一致性哈希映射稳定性 ===== */
|
||
|
||
/**
|
||
* @test test_consistent_hash_stability
|
||
* @brief 相同 key 总是映射到相同后端
|
||
*/
|
||
void test_consistent_hash_stability(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_CONSISTENT_HASH);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 4);
|
||
|
||
lb_build_hash_ring(&lb.hash_ring, &rule);
|
||
|
||
/**< 多次选择相同 key */
|
||
int idx1 = lb_select_backend(&lb, &rule, "/api/resource/42");
|
||
int idx2 = lb_select_backend(&lb, &rule, "/api/resource/42");
|
||
int idx3 = lb_select_backend(&lb, &rule, "/api/resource/42");
|
||
|
||
TEST_ASSERT_EQUAL_INT(idx1, idx2);
|
||
TEST_ASSERT_EQUAL_INT(idx2, idx3);
|
||
TEST_ASSERT(idx1 >= 0 && idx1 < 4);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_consistent_hash_different_keys
|
||
* @brief 不同 key 可以映射到不同后端
|
||
*/
|
||
void test_consistent_hash_different_keys(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_CONSISTENT_HASH);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 4);
|
||
|
||
lb_build_hash_ring(&lb.hash_ring, &rule);
|
||
|
||
/**< 使用多个不同的 key */
|
||
int idx1 = lb_select_backend(&lb, &rule, "/api/users");
|
||
int idx2 = lb_select_backend(&lb, &rule, "/api/products");
|
||
int idx3 = lb_select_backend(&lb, &rule, "/api/orders");
|
||
|
||
TEST_ASSERT(idx1 >= 0 && idx1 < 4);
|
||
TEST_ASSERT(idx2 >= 0 && idx2 < 4);
|
||
TEST_ASSERT(idx3 >= 0 && idx3 < 4);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_consistent_hash_null_key_fallback
|
||
* @brief NULL key 回退到随机选择
|
||
*/
|
||
void test_consistent_hash_null_key_fallback(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_CONSISTENT_HASH);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
lb_build_hash_ring(&lb.hash_ring, &rule);
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT(idx >= 0 && idx < 3);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_consistent_hash_distribution
|
||
* @brief 多个 key 的分布检查(不应全部映射到同一后端)
|
||
*/
|
||
void test_consistent_hash_distribution(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_CONSISTENT_HASH);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 4);
|
||
|
||
lb_build_hash_ring(&lb.hash_ring, &rule);
|
||
|
||
/**< 使用大量不同的 key 测试分布 */
|
||
int count[4] = {0, 0, 0, 0};
|
||
char key[64];
|
||
for (int i = 0; i < 1000; i++) {
|
||
snprintf(key, sizeof(key), "/api/item/%d", i);
|
||
int idx = lb_select_backend(&lb, &rule, key);
|
||
TEST_ASSERT(idx >= 0 && idx < 4);
|
||
count[idx]++;
|
||
}
|
||
|
||
/**< 每个后端都应被分配到一些 key(分布应相对均匀) */
|
||
for (int i = 0; i < 4; i++) {
|
||
TEST_ASSERT_MESSAGE(count[i] > 50,
|
||
"一致性哈希分布不均匀,某个后端分配过少");
|
||
}
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 6: 最少连接算法 ===== */
|
||
|
||
/**
|
||
* @test test_least_connections_selects_min
|
||
* @brief 最少连接选择连接数最少的后端
|
||
*/
|
||
void test_least_connections_selects_min(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
/**< 设置不同的活跃连接数 */
|
||
lb.stats[0].active_connections = 5;
|
||
lb.stats[1].active_connections = 2; /**< 最少 */
|
||
lb.stats[2].active_connections = 8;
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_least_connections_all_zero
|
||
* @brief 所有连接数为 0 时选择第一个健康后端
|
||
*/
|
||
void test_least_connections_all_zero(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_least_connections_skips_unhealthy
|
||
* @brief 最少连接跳过不健康后端
|
||
*/
|
||
void test_least_connections_skips_unhealthy(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
rule.backends[0].healthy = false;
|
||
|
||
/**< backend0 连接数最少,但不健康 */
|
||
lb.stats[0].active_connections = 0;
|
||
lb.stats[1].active_connections = 3;
|
||
lb.stats[2].active_connections = 5;
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_least_connections_no_healthy
|
||
* @brief 没有健康后端返回 -1
|
||
*/
|
||
void test_least_connections_no_healthy(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
for (size_t i = 0; i < 3; i++) {
|
||
rule.backends[i].healthy = false;
|
||
}
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_least_connections_tie_break
|
||
* @brief 连接数相同时选择索引小的(确定性)
|
||
*/
|
||
void test_least_connections_tie_break(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
/**< 所有后端连接数相同 */
|
||
lb.stats[0].active_connections = 3;
|
||
lb.stats[1].active_connections = 3;
|
||
lb.stats[2].active_connections = 3;
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx); /**< 第一个达到最小值的后端 */
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 7: 统计更新 ===== */
|
||
|
||
/**
|
||
* @test test_stats_request_start
|
||
* @brief 请求开始正确更新连接数和请求数
|
||
*/
|
||
void test_stats_request_start(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].total_requests);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
TEST_ASSERT_EQUAL_UINT32(2, lb.stats[0].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(2, lb.stats[0].total_requests);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_stats_request_end_success
|
||
* @brief 请求成功正确更新统计
|
||
*/
|
||
void test_stats_request_end_success(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].active_connections);
|
||
|
||
lb_update_stats_request_end(&lb, 0, true, 1000);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[0].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT64(1000, lb.stats[0].last_response_time_us);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[0].total_failures);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_stats_request_end_failure
|
||
* @brief 请求失败正确记录失败数
|
||
*/
|
||
void test_stats_request_end_failure(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
lb_update_stats_request_end(&lb, 0, false, 500);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].total_failures);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
lb_update_stats_request_end(&lb, 0, false, 600);
|
||
TEST_ASSERT_EQUAL_UINT32(2, lb.stats[0].total_failures);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_stats_end_without_start
|
||
* @brief 未调用 start 直接调用 end 不会下溢
|
||
*/
|
||
void test_stats_end_without_start(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
/**< active_connections 已经是 0 */
|
||
lb_update_stats_request_end(&lb, 0, true, 1000);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[0].active_connections);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_stats_multiple_backends
|
||
* @brief 多后端统计互不干扰
|
||
*/
|
||
void test_stats_multiple_backends(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
lb_update_stats_request_start(&lb, 0);
|
||
lb_update_stats_request_start(&lb, 1);
|
||
lb_update_stats_request_start(&lb, 1);
|
||
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(2, lb.stats[1].active_connections);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 8: EWMA 计算 ===== */
|
||
|
||
/**
|
||
* @test test_ewma_first_update
|
||
* @brief 首次 EWMA 更新直接使用当前值
|
||
*/
|
||
void test_ewma_first_update(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
lb_update_stats_request_end(&lb, 0, true, 1000);
|
||
/**< 首次更新:ewma 直接设为当前值 */
|
||
TEST_ASSERT_EQUAL_UINT64(1000, lb.stats[0].ewma_response_time_us);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_ewma_subsequent_updates
|
||
* @brief 后续 EWMA 更新应用平滑公式
|
||
*
|
||
* alpha = 80,即 new = 0.8 * current + 0.2 * old
|
||
* old = 1000, current = 2000
|
||
* new = (80 * 2000 + 20 * 1000) / 100 = (160000 + 20000) / 100 = 1800
|
||
*/
|
||
void test_ewma_subsequent_updates(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
lb_update_stats_request_end(&lb, 0, true, 1000); /**< 首次:ewma=1000 */
|
||
lb_update_stats_request_end(&lb, 0, true, 2000); /**< 第二次:ewma=1800 */
|
||
|
||
TEST_ASSERT_EQUAL_UINT64(1800, lb.stats[0].ewma_response_time_us);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_ewma_converges
|
||
* @brief EWMA 向稳定值收敛
|
||
*
|
||
* 多次更新相同值后,EWMA 应接近该值。
|
||
*/
|
||
void test_ewma_converges(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
lb_update_stats_request_end(&lb, 0, true, 1000); /**< 首次 */
|
||
for (int i = 0; i < 20; i++) {
|
||
lb_update_stats_request_end(&lb, 0, true, 1000);
|
||
}
|
||
|
||
/**< 经过 20 次相同值更新,EWMA 应非常接近 1000 */
|
||
TEST_ASSERT_UINT64_WITHIN(10, 1000,
|
||
lb.stats[0].ewma_response_time_us);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_ewma_alpha_effect
|
||
* @brief alpha 值影响平滑程度
|
||
*
|
||
* alpha 较大时(接近 100),对新值更敏感。
|
||
*/
|
||
void test_ewma_alpha_effect(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
lb.alpha = 90; /**< 更高 alpha,更敏感 */
|
||
|
||
lb_update_stats_request_end(&lb, 0, true, 1000);
|
||
lb_update_stats_request_end(&lb, 0, true, 2000);
|
||
|
||
/**< alpha=90: new = (90*2000 + 10*1000)/100 = 1900 */
|
||
TEST_ASSERT_EQUAL_UINT64(1900, lb.stats[0].ewma_response_time_us);
|
||
|
||
lb.alpha = 50; /**< 更低 alpha,更平滑 */
|
||
lb_update_stats_request_end(&lb, 0, true, 2000);
|
||
|
||
/**< alpha=50: new = (50*2000 + 50*1900)/100 = 1950 */
|
||
TEST_ASSERT_EQUAL_UINT64(1950, lb.stats[0].ewma_response_time_us);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 9: 加权响应时间选择 ===== */
|
||
|
||
/**
|
||
* @test test_weighted_response_selects_lowest_ratio
|
||
* @brief 选择 EWMA/weight 比值最小的后端
|
||
*/
|
||
void test_weighted_response_selects_lowest_ratio(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
uint32_t weights[] = {1, 1, 1};
|
||
make_weighted_rule(&rule, 3, weights);
|
||
|
||
/**< backend1 的 EWMA 最低 */
|
||
lb.stats[0].ewma_response_time_us = 5000;
|
||
lb.stats[1].ewma_response_time_us = 1000; /**< 最优 */
|
||
lb.stats[2].ewma_response_time_us = 3000;
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_weighted_response_weight_influence
|
||
* @brief 权重影响选择(高权重可抵消高 EWMA)
|
||
*
|
||
* backend0: ewma=5000, weight=5 => score=1000
|
||
* backend1: ewma=3000, weight=1 => score=3000
|
||
* backend2: ewma=4000, weight=2 => score=2000
|
||
*
|
||
* backend0 应被选中(最低 score)
|
||
*/
|
||
void test_weighted_response_weight_influence(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
uint32_t weights[] = {5, 1, 2};
|
||
make_weighted_rule(&rule, 3, weights);
|
||
|
||
lb.stats[0].ewma_response_time_us = 5000; /**< weight=5, score=1000 */
|
||
lb.stats[1].ewma_response_time_us = 3000; /**< weight=1, score=3000 */
|
||
lb.stats[2].ewma_response_time_us = 4000; /**< weight=2, score=2000 */
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_weighted_response_zero_weight
|
||
* @brief weight=0 时按 weight=1 处理(防止除零)
|
||
*/
|
||
void test_weighted_response_zero_weight(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
uint32_t weights[] = {0, 1}; /**< backend0 weight=0 */
|
||
make_weighted_rule(&rule, 2, weights);
|
||
|
||
lb.stats[0].ewma_response_time_us = 1000;
|
||
lb.stats[1].ewma_response_time_us = 2000;
|
||
|
||
/**< backend0: 1000/1=1000, backend1: 2000/1=2000 */
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 10: 随机算法 ===== */
|
||
|
||
/**
|
||
* @test test_random_returns_valid_index
|
||
* @brief 随机选择返回有效后端索引
|
||
*/
|
||
void test_random_returns_valid_index(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 5);
|
||
|
||
for (int i = 0; i < 50; i++) {
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT(idx >= 0 && idx < 5);
|
||
}
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_random_distribution
|
||
* @brief 随机选择分布大致均匀
|
||
*/
|
||
void test_random_distribution(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 4);
|
||
|
||
int count[4] = {0, 0, 0, 0};
|
||
for (int i = 0; i < 4000; i++) {
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
count[idx]++;
|
||
}
|
||
|
||
/**< 每个后端应被选中约 1000 次,允许 ±200 偏差 */
|
||
for (int i = 0; i < 4; i++) {
|
||
TEST_ASSERT_INT_WITHIN(300, 1000, count[i]);
|
||
}
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_random_no_healthy
|
||
* @brief 没有健康后端时返回 -1
|
||
*/
|
||
void test_random_no_healthy(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
for (size_t i = 0; i < 3; i++) {
|
||
rule.backends[i].healthy = false;
|
||
}
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 11: 边界条件和错误处理 ===== */
|
||
|
||
/**
|
||
* @test test_select_null_lb
|
||
* @brief NULL 负载均衡器返回 -1
|
||
*/
|
||
void test_select_null_lb(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 2);
|
||
|
||
int idx = lb_select_backend(NULL, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
}
|
||
|
||
/**
|
||
* @test test_select_null_rule
|
||
* @brief NULL 规则返回 -1
|
||
*/
|
||
void test_select_null_rule(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
|
||
int idx = lb_select_backend(&lb, NULL, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_select_empty_rule
|
||
* @brief 空规则(无后端)返回 -1
|
||
*/
|
||
void test_select_empty_rule(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_RANDOM);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
memset(&rule, 0, sizeof(rule));
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_update_stats_null_lb
|
||
* @brief NULL 负载均衡器更新统计不崩溃
|
||
*/
|
||
void test_update_stats_null_lb(void) {
|
||
/**< 应安全返回,不崩溃 */
|
||
lb_update_stats_request_start(NULL, 0);
|
||
lb_update_stats_request_end(NULL, 0, true, 100);
|
||
TEST_PASS_MESSAGE("NULL lb stats update handled safely");
|
||
}
|
||
|
||
/**
|
||
* @test test_round_robin_returns_neg_one
|
||
* @brief 轮询算法返回 -1(由调用者使用 proxy.c 的 SWW)
|
||
*/
|
||
void test_round_robin_returns_neg_one(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_ROUND_ROBIN);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(-1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/* ===== 测试组 12: 综合集成测试 ===== */
|
||
|
||
/**
|
||
* @test test_full_request_lifecycle_least_connections
|
||
* @brief 最少连接完整请求生命周期
|
||
*/
|
||
void test_full_request_lifecycle_least_connections(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_LEAST_CONNECTIONS);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
/**< 初始选择:backend0(连接数都是 0,选第一个) */
|
||
int idx1 = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx1);
|
||
|
||
/**< 请求开始 */
|
||
lb_update_stats_request_start(&lb, (size_t)idx1);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[0].active_connections);
|
||
|
||
/**< 第二次选择:backend1(backend0 已有 1 个连接) */
|
||
int idx2 = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, idx2);
|
||
lb_update_stats_request_start(&lb, (size_t)idx2);
|
||
|
||
/**< 第三次选择:backend2 */
|
||
int idx3 = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(2, idx3);
|
||
lb_update_stats_request_start(&lb, (size_t)idx3);
|
||
|
||
/**< 请求结束 */
|
||
lb_update_stats_request_end(&lb, (size_t)idx1, true, 500);
|
||
lb_update_stats_request_end(&lb, (size_t)idx2, true, 600);
|
||
lb_update_stats_request_end(&lb, (size_t)idx3, false, 100);
|
||
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[0].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[1].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(0, lb.stats[2].active_connections);
|
||
TEST_ASSERT_EQUAL_UINT32(1, lb.stats[2].total_failures);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_full_request_lifecycle_weighted_response
|
||
* @brief 加权响应时间完整生命周期
|
||
*/
|
||
void test_full_request_lifecycle_weighted_response(void) {
|
||
cocoon_load_balancer_t lb;
|
||
lb_init(&lb, COCOON_LB_WEIGHTED_RESPONSE);
|
||
|
||
cocoon_proxy_rule_t rule;
|
||
uint32_t weights[] = {2, 1};
|
||
make_weighted_rule(&rule, 2, weights);
|
||
|
||
/**< 初始无 EWMA,两个后端都是 0,选第一个健康后端 */
|
||
int idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(0, idx);
|
||
|
||
/**< 模拟请求并记录不同响应时间 */
|
||
lb_update_stats_request_start(&lb, 0);
|
||
lb_update_stats_request_end(&lb, 0, true, 2000);
|
||
|
||
lb_update_stats_request_start(&lb, 1);
|
||
lb_update_stats_request_end(&lb, 1, true, 500);
|
||
|
||
/**< backend0: ewma=2000, weight=2 => score=1000 */
|
||
/**< backend1: ewma=500, weight=1 => score=500 */
|
||
/**< 应选择 backend1 */
|
||
idx = lb_select_backend(&lb, &rule, NULL);
|
||
TEST_ASSERT_EQUAL_INT(1, idx);
|
||
|
||
lb_destroy(&lb);
|
||
}
|
||
|
||
/**
|
||
* @test test_virtual_node_distribution_uniform
|
||
* @brief 虚拟节点在环上均匀分布(哈希值范围覆盖)
|
||
*/
|
||
void test_virtual_node_distribution_uniform(void) {
|
||
cocoon_proxy_rule_t rule;
|
||
make_test_rule(&rule, 3);
|
||
|
||
cocoon_hash_ring_t ring;
|
||
memset(&ring, 0, sizeof(ring));
|
||
lb_build_hash_ring(&ring, &rule);
|
||
|
||
/**< 检查最小和最大哈希值存在合理差距 */
|
||
uint32_t min_hash = ring.nodes[0].node_hash;
|
||
uint32_t max_hash = ring.nodes[ring.node_count - 1].node_hash;
|
||
|
||
/**< 哈希值应分布在较大范围内 */
|
||
TEST_ASSERT(min_hash < max_hash);
|
||
TEST_ASSERT_UINT32_WITHIN(0xFFFFFFFFU / 4, 0x80000000U,
|
||
min_hash + (max_hash - min_hash) / 2);
|
||
}
|
||
|
||
/* ===== 主函数 ===== */
|
||
|
||
int main(void) {
|
||
UNITY_BEGIN();
|
||
|
||
/* 测试组 1: MurmurHash3 */
|
||
RUN_TEST(test_hash_known_values);
|
||
RUN_TEST(test_hash_empty_string_zero_length);
|
||
RUN_TEST(test_hash_null_returns_zero);
|
||
RUN_TEST(test_hash_consistency);
|
||
RUN_TEST(test_hash_different_inputs);
|
||
RUN_TEST(test_hash_binary_data);
|
||
|
||
/* 测试组 2: lb_init / lb_destroy */
|
||
RUN_TEST(test_init_sets_algorithm);
|
||
RUN_TEST(test_init_default_alpha);
|
||
RUN_TEST(test_init_clears_stats);
|
||
RUN_TEST(test_init_clears_hash_ring);
|
||
RUN_TEST(test_destroy_cleans_up);
|
||
|
||
/* 测试组 3: lb_algorithm_name */
|
||
RUN_TEST(test_algorithm_name_all);
|
||
RUN_TEST(test_algorithm_name_unknown);
|
||
|
||
/* 测试组 4: 一致性哈希环 */
|
||
RUN_TEST(test_build_hash_ring_creates_nodes);
|
||
RUN_TEST(test_build_hash_ring_empty_rule);
|
||
RUN_TEST(test_build_hash_ring_skips_unhealthy);
|
||
RUN_TEST(test_build_hash_ring_all_unhealthy);
|
||
RUN_TEST(test_hash_ring_nodes_sorted);
|
||
RUN_TEST(test_hash_ring_backend_coverage);
|
||
RUN_TEST(test_pick_from_ring_basic);
|
||
RUN_TEST(test_pick_from_ring_wraparound);
|
||
RUN_TEST(test_pick_from_ring_zero_hash);
|
||
RUN_TEST(test_pick_from_ring_deterministic);
|
||
RUN_TEST(test_pick_from_ring_empty);
|
||
|
||
/* 测试组 5: 一致性哈希映射稳定性 */
|
||
RUN_TEST(test_consistent_hash_stability);
|
||
RUN_TEST(test_consistent_hash_different_keys);
|
||
RUN_TEST(test_consistent_hash_null_key_fallback);
|
||
RUN_TEST(test_consistent_hash_distribution);
|
||
|
||
/* 测试组 6: 最少连接算法 */
|
||
RUN_TEST(test_least_connections_selects_min);
|
||
RUN_TEST(test_least_connections_all_zero);
|
||
RUN_TEST(test_least_connections_skips_unhealthy);
|
||
RUN_TEST(test_least_connections_no_healthy);
|
||
RUN_TEST(test_least_connections_tie_break);
|
||
|
||
/* 测试组 7: 统计更新 */
|
||
RUN_TEST(test_stats_request_start);
|
||
RUN_TEST(test_stats_request_end_success);
|
||
RUN_TEST(test_stats_request_end_failure);
|
||
RUN_TEST(test_stats_end_without_start);
|
||
RUN_TEST(test_stats_multiple_backends);
|
||
|
||
/* 测试组 8: EWMA 计算 */
|
||
RUN_TEST(test_ewma_first_update);
|
||
RUN_TEST(test_ewma_subsequent_updates);
|
||
RUN_TEST(test_ewma_converges);
|
||
RUN_TEST(test_ewma_alpha_effect);
|
||
|
||
/* 测试组 9: 加权响应时间选择 */
|
||
RUN_TEST(test_weighted_response_selects_lowest_ratio);
|
||
RUN_TEST(test_weighted_response_weight_influence);
|
||
RUN_TEST(test_weighted_response_zero_weight);
|
||
|
||
/* 测试组 10: 随机算法 */
|
||
RUN_TEST(test_random_returns_valid_index);
|
||
RUN_TEST(test_random_distribution);
|
||
RUN_TEST(test_random_no_healthy);
|
||
|
||
/* 测试组 11: 边界条件 */
|
||
RUN_TEST(test_select_null_lb);
|
||
RUN_TEST(test_select_null_rule);
|
||
RUN_TEST(test_select_empty_rule);
|
||
RUN_TEST(test_update_stats_null_lb);
|
||
RUN_TEST(test_round_robin_returns_neg_one);
|
||
|
||
/* 测试组 12: 综合集成 */
|
||
RUN_TEST(test_full_request_lifecycle_least_connections);
|
||
RUN_TEST(test_full_request_lifecycle_weighted_response);
|
||
RUN_TEST(test_virtual_node_distribution_uniform);
|
||
|
||
return UNITY_END();
|
||
}
|