/* test_attach.c - /include 的暂存、glob 展开与体积/类型判断 * * 交互式挑选 (attach_pick)要真终端才能测, 由 e2e 第 55 段覆盖; * 这里测的是不需要人操作的那一半. */ #include "attach.h" #include "image.h" #include "json.h" #include "util.h" #include #include #include #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) static void write_file(const char *path, const void *data, size_t n) { FILE *f = fopen(path, "wb"); if (!f) { perror(path); exit(2); } if (n && fwrite(data, 1, n, f) != n) { perror("fwrite"); exit(2); } fclose(f); } static const unsigned char PNG[] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; /* 数一数暂存里某类块有几个 */ static size_t count_type(const JsonValue *parts, const char *type) { if (!parts) return 0; size_t n = 0; for (size_t i = 0; i < json_array_size((JsonValue *)parts); i++) { const char *t = json_get_string(json_array_get((JsonValue *)parts, i), "type"); if (t && strcmp(t, type) == 0) n++; } return n; } /* 记一下回调被问了几次 —— 它只在遇到图片时该被问到. * 答案和计数放在一起, 以免"计数版只实现了一种答案"这种测试自己的错. */ static int ask_count; static int ask_answer; static int counting_ask(void *ud) { (void)ud; ask_count++; return ask_answer; } int main(void) { char tmpl[] = "/tmp/ligent_attach_XXXXXX"; char *dir = mkdtemp(tmpl); if (!dir) { perror("mkdtemp"); return 2; } /* 备份目录也指到临时目录, 别污染仓库 */ image_set_bindir(dir); char *f_c = xasprintf("%s/a.c", dir); char *f_txt = xasprintf("%s/b.txt", dir); char *f_bin = xasprintf("%s/bin.dat", dir); char *f_png = xasprintf("%s/pic.png", dir); char *f_big = xasprintf("%s/big.txt", dir); write_file(f_c, "int main(void){return 0;}\n", 26); write_file(f_txt, "hello\n", 6); write_file(f_bin, "\x00\x01\x02binary", 9); write_file(f_png, PNG, sizeof PNG); { /* 超过单个文件上限 (256 KB) */ size_t n = ATTACH_MAX_FILE + 1024; char *big = xmalloc(n); memset(big, 'x', n); write_file(f_big, big, n); free(big); } puts("glob 展开: 命中几个就暂存几个"); attach_clear(); size_t skipped = 0; char err[256]; int added = attach_glob(f_c, NULL, NULL, &skipped, err, sizeof err); CHECK(added == 1 && attach_count() == 1, "命中一个文件"); CHECK(skipped == 0, "没有跳过任何东西"); JsonValue *parts = attach_take_parts(); CHECK(parts && json_array_size(parts) == 1, "取到 1 个块"); JsonValue *p0 = parts ? json_array_get(parts, 0) : NULL; CHECK(p0 && strcmp(json_get_string(p0, "type"), "text") == 0, "文本文件应当是 text 块"); const char *txt = p0 ? json_get_string(p0, "text") : NULL; CHECK(txt && strstr(txt, "int main") != NULL, "内容带上了"); CHECK(txt && strncmp(txt, "===== ", 6) == 0, "块开头标了来源路径"); CHECK(txt && strchr(txt, '\n') != NULL, "结尾有换行"); /* 只在本地记的来源, 绝不能进块里 (那会跟着发给服务端) */ CHECK(p0 && json_get_string(p0, "source") == NULL, "块里不该有自造的 source 字段"); json_free(parts); CHECK(attach_count() == 0, "取走之后暂存清空"); puts("取走即清空, clear 也是"); attach_glob(f_c, NULL, NULL, &skipped, err, sizeof err); CHECK(attach_count() == 1, "再附带一次"); attach_clear(); CHECK(attach_count() == 0, "attach_clear 清空"); CHECK(attach_take_parts() == NULL, "清空之后取不到东西"); puts("二进制文件跳过"); attach_clear(); added = attach_glob(f_bin, NULL, NULL, &skipped, err, sizeof err); CHECK(added == 0 && skipped == 1, "二进制被跳过 (added=%d skipped=%zu)", added, skipped); puts("超过单个文件上限的跳过"); attach_clear(); added = attach_glob(f_big, NULL, NULL, &skipped, err, sizeof err); CHECK(added == 0 && skipped == 1, "太大的文本被跳过 (added=%d)", added); puts("图片: 认内容不认扩展名, 并走备份引用"); attach_clear(); added = attach_glob(f_png, NULL, NULL, &skipped, err, sizeof err); CHECK(added == 1, "png 被收下"); CHECK(attach_has_image(), "暂存里有图片"); parts = attach_take_parts(); CHECK(count_type(parts, "ligent_image") == 1, "应当是一个 ligent_image 引用块"); json_free(parts); puts("模型不收图时跳过图片 (回调只在遇到图片时被问)"); attach_clear(); ask_count = 0; ask_answer = 1; added = attach_glob(f_txt, counting_ask, NULL, &skipped, err, sizeof err); CHECK(added == 1, "纯文本照带"); CHECK(ask_count == 0, "纯文本不该去问'能不能收图' (问了一次就是白探测)"); attach_clear(); ask_count = 0; ask_answer = 0; added = attach_glob(f_png, counting_ask, NULL, &skipped, err, sizeof err); CHECK(added == 0 && skipped == 1, "回调说不行就跳过"); CHECK(ask_count == 1, "遇到图片时问了一次, 实际 %d", ask_count); puts("匹配不到 / 参数非法"); attach_clear(); added = attach_glob("/tmp/绝对不存在的模式*.zzz", NULL, NULL, &skipped, err, sizeof err); CHECK(added == -1 && err[0], "匹配不到返回 -1 并给出原因: %s", err); added = attach_glob(NULL, NULL, NULL, &skipped, err, sizeof err); CHECK(added == -1, "NULL 模式返回 -1"); added = attach_glob("", NULL, NULL, &skipped, err, sizeof err); CHECK(added == -1, "空模式返回 -1"); puts("多个匹配会累加"); attach_clear(); char *pat = xasprintf("%s/*.txt", dir); added = attach_glob(pat, NULL, NULL, &skipped, err, sizeof err); CHECK(added == 1, "只有 b.txt 能带 (big.txt 超限), 实际 %d", added); CHECK(skipped == 1, "big.txt 被跳过"); free(pat); attach_clear(); free(f_c); free(f_txt); free(f_bin); free(f_png); free(f_big); image_set_bindir("."); char *cmd = xasprintf("rm -rf %s", tmpl); if (system(cmd) != 0) { } free(cmd); printf("\n%d 项检查, %d 项失败\n", checks, failures); return failures ? 1 : 0; }