/* test_capability.c - 能力探测的纯逻辑部分 * * 探测序列本身(基线 -> 合并 -> 拆开)要发 HTTP,属于端到端范畴, * 由 e2e 第 51 段对着 mock 覆盖:正常模型 / 不收图片 / 不收 tools / * 探测失败(401)/ --no-probe 五种走法都在那儿验。 * * 这里只测不需要网络的:名字映射、关闭探测时的语义、缓存查询。 * 其中"关闭探测必须给未测而不是不支持"那条是回归测试 —— 那句话看着像废话, * 但 CAP_NO 恰好是 0,而 Agent 是 memset 出来的,忘了显式初始化就会一声不响地 * 把所有工具摘掉。这个 bug 真的发生过一次。 */ #include "capability.h" #include "llm.h" #include #include static int failures = 0; static int checks = 0; #define CHECK(cond, ...) \ do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" ✗ %s:%d: ", __FILE__, __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) int main(void) { /* CAP_NO 必须是 0 —— Agent 是 memset 出来的,整个 capability 模块的 * 设计都建立在"零值不能是某个真实结论"之上。这条断言是那个 bug 的护栏。 */ CHECK(CAP_NO == 0, "CAP_NO 应为 0(零值即'不支持'是事故的根源)"); CHECK(CAP_UNKNOWN != CAP_NO, "未知与不支持必须是两个不同的值"); CHECK(CAP_UNKNOWN != CAP_YES, "未知与支持必须是两个不同的值"); puts("名字映射"); CHECK(strcmp(cap_name(CAP_YES), "支持") == 0, "YES"); CHECK(strcmp(cap_name(CAP_NO), "不支持") == 0, "NO"); CHECK(strcmp(cap_name(CAP_UNKNOWN), "未测") == 0, "UNKNOWN"); /* 任何不是 YES/NO 的值都该落到"未测",而不是碰巧落到某一个 */ CHECK(strcmp(cap_name(42), "未测") == 0, "越界值应落到未测"); puts("关掉探测:一律未测,绝不冒充结论"); /* 用可变数组而不是字面量:LlmConfig 的这些字段是 char *(真要释放的), * 而 -Wwrite-strings 下 "字面量" 是 const char * —— 直接赋值会被警告。 */ char model[] = "some-model"; char base_url[] = "http://127.0.0.1:1/v1"; LlmConfig cfg; memset(&cfg, 0, sizeof cfg); cfg.model = model; cfg.base_url = base_url; cfg.timeout = 1; cap_cache_clear(); Capabilities c; memset(&c, 0xAB, sizeof c); /* 故意填脏,验证会被完整覆盖 */ int probed = cap_get(&cfg, 0, &c, 0, NULL, 0); CHECK(probed == 0, "被禁用时不该声称探测过"); CHECK(c.probed == 0, "probed 应为 0"); CHECK(c.vision == CAP_UNKNOWN, "禁用时 vision 应为未测,实际 %d", c.vision); CHECK(c.tools == CAP_UNKNOWN, "禁用时 tools 应为未测,实际 %d", c.tools); CHECK(c.conclusive == 0, "禁用时不该声称有结论"); /* 关键回归:禁用时的结果拿去决定工具表,必须"全都留着" */ CHECK(c.vision != CAP_NO, "禁用时不能表现为不支持(否则会藏掉 read_image)"); CHECK(c.tools != CAP_NO, "禁用时不能表现为不支持(否则会摘掉整张工具表)"); puts("缓存查询"); Capabilities got; memset(&got, 0xAB, sizeof got); CHECK(cap_cached(&cfg, &got) == 0, "没测过时缓存应未命中"); /* 关掉探测不会写入缓存 */ CHECK(cap_cached(&cfg, NULL) == 0, "禁用不该往缓存里写东西"); cap_cache_clear(); CHECK(cap_cached(&cfg, &got) == 0, "清空之后仍应未命中"); /* 空指针不该崩 */ cap_get(NULL, 0, &c, 0, NULL, 0); CHECK(c.vision == CAP_UNKNOWN, "cfg 为空也要给未测"); cap_get(&cfg, 0, NULL, 0, NULL, 0); cap_cache_clear(); CHECK(1, "空指针路径没有崩"); printf("\n%d 项检查,%d 项失败\n", checks, failures); return failures ? 1 : 0; }