Files
ligent/tests/test_archive.c
T
2026-10-03 14:13:21 +08:00

179 lines
7.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_archive.c - 存档命名规则与读写往返(不需要网络) */
#include "archive.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
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 int is_alnum_lower(const char *s, size_t n)
{
for (size_t i = 0; i < n; i++)
if (!((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= '0' && s[i] <= '9')))
return 0;
return 1;
}
int main(void)
{
char tmpl[] = "/tmp/ligent_arch_XXXXXX";
char *dir = mkdtemp(tmpl);
if (!dir) { perror("mkdtemp"); return 2; }
char err[256];
puts("根名:8 位小写字母数字");
char *n1 = NULL;
CHECK(archive_next_name(dir, NULL, NULL, 0, &n1, err, sizeof err) == 0, "生成失败: %s", err);
CHECK(n1 && strlen(n1) == ARCHIVE_ROOT_LEN, "根名应为 %d 位,实际 %s",
ARCHIVE_ROOT_LEN, n1 ? n1 : "(null)");
CHECK(n1 && is_alnum_lower(n1, strlen(n1)), "根名字符集应为 [a-z0-9]:%s", n1);
free(n1);
/* 空字符串与 NULL 等价:都表示"没有来源" */
char *n2 = NULL;
CHECK(archive_next_name(dir, "", NULL, 0, &n2, err, sizeof err) == 0, "空 lineage 应可用");
CHECK(n2 && strlen(n2) == ARCHIVE_ROOT_LEN, "空 lineage 也应给出根名");
free(n2);
puts("分支名:来源 + _ + 3 位");
char *b = NULL;
CHECK(archive_next_name(dir, "a1b2c3d4", NULL, 0, &b, err, sizeof err) == 0,
"分支生成失败: %s", err);
CHECK(b && strncmp(b, "a1b2c3d4_", 9) == 0, "应以来源加下划线开头:%s", b);
CHECK(b && strlen(b) == 8 + 1 + ARCHIVE_BRANCH_LEN, "分支长度不对:%s", b);
CHECK(b && is_alnum_lower(b + 9, ARCHIVE_BRANCH_LEN), "后缀字符集不对:%s", b);
free(b);
/* 多次生成应当不同(随机性) */
char *x1 = NULL, *x2 = NULL;
archive_next_name(dir, "a1b2c3d4", NULL, 0, &x1, err, sizeof err);
archive_next_name(dir, "a1b2c3d4", NULL, 0, &x2, err, sizeof err);
CHECK(x1 && x2 && strcmp(x1, x2) != 0, "两次生成不应相同:%s / %s", x1, x2);
free(x1); free(x2);
puts("长度上限:接不下就重新起根");
/* 边界:来源长度 + 1 + 3 + ".json" <= 240 */
size_t maxlineage = ARCHIVE_NAME_MAX - 1 - ARCHIVE_BRANCH_LEN - strlen(".json");
char *ok = xmalloc(maxlineage + 1);
memset(ok, 'a', maxlineage);
ok[maxlineage] = '\0';
char *edge = NULL;
CHECK(archive_next_name(dir, ok, NULL, 0, &edge, err, sizeof err) == 0, "边界生成失败");
CHECK(edge && strncmp(edge, "aaa", 3) == 0, "刚好装得下时应继续接分支");
CHECK(edge && strlen(edge) + strlen(".json") <= ARCHIVE_NAME_MAX,
"完整文件名不应超上限:%zu", edge ? strlen(edge) + 5 : 0);
free(edge);
/* 再多一位就装不下了 */
char *too = xmalloc(maxlineage + 2);
memset(too, 'a', maxlineage + 1);
too[maxlineage + 1] = '\0';
char *fresh = NULL;
CHECK(archive_next_name(dir, too, NULL, 0, &fresh, err, sizeof err) == 0, "超限生成失败");
CHECK(fresh && strlen(fresh) == ARCHIVE_ROOT_LEN,
"超限时应退回 %d 位根名,实际 %s(%zu 位)",
ARCHIVE_ROOT_LEN, fresh ? fresh : "(null)", fresh ? strlen(fresh) : 0);
free(fresh);
puts("前缀:临时存档");
char *tp = NULL;
CHECK(archive_next_name(dir, "a1b2c3d4", "tmp_", 0, &tp, err, sizeof err) == 0,
"带前缀生成失败");
CHECK(tp && strncmp(tp, "tmp_a1b2c3d4_", 13) == 0,
"前缀应在最前面、其后才是分支名:%s", tp ? tp : "(null)");
free(tp);
char *tr = NULL;
CHECK(archive_next_name(dir, NULL, "tmp_", 0, &tr, err, sizeof err) == 0, "带前缀建根失败");
CHECK(tr && strncmp(tr, "tmp_", 4) == 0 &&
strlen(tr) == 4 + ARCHIVE_ROOT_LEN,
"新根也应带前缀:%s", tr ? tr : "(null)");
free(tr);
/* 前缀要算进长度上限:原本刚好装得下的,加了 tmp_ 就该退回新根 */
char *edgep = NULL;
CHECK(archive_next_name(dir, ok, "tmp_", 0, &edgep, err, sizeof err) == 0, "边界失败");
CHECK(edgep && strlen(edgep) == 4 + ARCHIVE_ROOT_LEN,
"加了前缀装不下时应退回根名,实际 %s(%zu 位)",
edgep ? edgep : "(null)", edgep ? strlen(edgep) : 0);
free(edgep);
free(ok); free(too);
puts("导出与载入往返");
JsonValue *msgs = json_new_array();
JsonValue *m = json_new_object();
json_object_set_string(m, "role", "user");
json_object_set_string(m, "content", "你好,暗号是紫水晶");
json_array_push(msgs, m);
char *path = NULL, *name = NULL;
time_t t = 1700000000;
CHECK(archive_export(msgs, dir, "parentname", NULL, 0, t, t + 5,
"test-model", "http://example/v1",
&path, &name, err, sizeof err) == 0,
"导出失败: %s", err);
CHECK(name && strncmp(name, "parentname_", 11) == 0,
"导出名应挂在来源下:%s", name ? name : "(null)");
CHECK(path && access(path, R_OK) == 0, "文件应存在:%s", path ? path : "");
char *created = NULL, *saved = NULL, *model = NULL, *iname = NULL, *iparent = NULL;
JsonValue *back = archive_load(path, &created, &saved, &model, &iname, &iparent,
err, sizeof err);
CHECK(back != NULL, "载入失败: %s", err);
CHECK(back && json_array_size(back) == 1, "消息条数应为 1");
CHECK(iname && name && strcmp(iname, name) == 0, "内容里的 name 应与文件名一致");
CHECK(iparent && strcmp(iparent, "parentname") == 0,
"内容里的 parent 应记下来源:%s", iparent ? iparent : "(null)");
CHECK(model && strcmp(model, "test-model") == 0, "model 应往返");
CHECK(created && strstr(created, "2023-") != NULL,
"created 应可读:%s", created ? created : "(null)");
/* saved 是"这份存档写出时的时间",与 created(这段对话的开始时间)是两回事 */
CHECK(saved && strstr(saved, "2023-") != NULL,
"saved 应可读:%s", saved ? saved : "(null)");
json_free(back); json_free(msgs);
free(created); free(saved); free(model); free(iname); free(iparent);
puts("列举");
ArchiveEntry *list = NULL;
size_t n = archive_list(dir, &list);
CHECK(n == 1, "应有 1 份存档,实际 %zu", n);
if (n == 1) {
CHECK(strncmp(list[0].name, "parentname_", 11) == 0, "名字应保留");
CHECK(list[0].count == 1, "条数应为 1,实际 %ld", list[0].count);
CHECK(list[0].created != NULL, "时间应从内容里读出来");
}
archive_list_free(list, n);
/* 非 ligent 存档(没有 format 标记)应被忽略 */
char *jd = xasprintf("%s/junk.json", dir);
FILE *j2 = fopen(jd, "w");
if (j2) { fputs("{\"hello\":1}", j2); fclose(j2); }
n = archive_list(dir, &list);
CHECK(n == 1, "非 ligent 存档应被忽略,实际 %zu", n);
archive_list_free(list, n);
unlink(jd); free(jd);
unlink(path); free(path); free(name);
rmdir(dir);
printf("\n%d 项检查,%d 项失败\n", checks, failures);
return failures ? 1 : 0;
}