/* test_notif_tf.c — 宿主单测:通知#6 TF 转储(CSV 转义 / 10MB 滚动截断 / init 清板载+内存表)。 * * 直接 #include "meow_notif.c"(同 TU 平移其 static 常量/函数),stub 掉 meow_settings_get_int * (配额读——返回负=键缺失即用默认,涵盖 CSV/截断/init 逻辑)。宿主 gcc 编译运行即可,不进固件。 * * 运行:gcc -D_GNU_SOURCE -o test_notif_tf test_notif_tf.c && ./test_notif_tf */ #define _GNU_SOURCE #include #include #include #include #include /* stub:meow_notif.c 的 read_quota() 读 notify.* 配额。<0 => 键缺失=> 用默认值 */ int meow_settings_get_int(const char *key, int *out) { (void)key; (void)out; return -2; /* MEOW_ERR_NOENT */ } #include "meow_notif.c" /* 把全部实现(含 statics)收进本 TU */ static int g_fail = 0; #define CHECK(cond, msg) do { \ if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail++; } \ else fprintf(stderr, "ok: %s\n", msg); \ } while (0) static char *read_all(const char *path, long *out_len) { FILE *f = fopen(path, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); char *s = malloc((size_t)n + 1); if (!s) { fclose(f); return NULL; } size_t got = fread(s, 1, (size_t)n, f); s[got] = 0; fclose(f); if (out_len) *out_len = n; return s; } static void make_notif(meow_notif_t *n, uint32_t id, const char *sum, const char *body) { memset(n, 0, sizeof *n); n->id = id; n->created_ms = 1234567890123ULL; strcpy(n->app_id, "com.app"); strcpy(n->category, "general"); strcpy(n->summary, sum); strcpy(n->body, body); strcpy(n->target_activity, "act"); n->urgency = 0; /* 0=LOW(缺省值) */ n->progress = -1; /* 无进度 */ n->ongoing = 1; } /* 回读指定 summary 的活跃通知(meow_notif_list 回调;按 g_find_key 匹配) */ static meow_notif_t g_got; static int g_got_found = 0; static char g_find_key[64]; static int find_cb(const meow_notif_t *n, void *ctx) { (void)ctx; if (n && strcmp(n->summary, g_find_key) == 0) { g_got = *n; g_got_found = 1; return 1; } return 0; } static meow_notif_t find_notif(const char *summary) { g_got_found = 0; memset(&g_got, 0, sizeof g_got); snprintf(g_find_key, sizeof g_find_key, "%s", summary); meow_notif_list(NULL, find_cb, NULL); return g_got_found ? g_got : (meow_notif_t){0}; } /* 图标回退 stub:com.app → "app_icon_name";其他 app → 无 */ static int stub_icon_cb(const char *app_id, char *out, size_t outsz) { if (app_id && !strcmp(app_id, "com.app")) { snprintf(out, outsz, "app_icon_name"); return 1; } return 0; } /* dump_cb 类型是 void,而 meow_notif_dump_tf 返回 int——包一层(同 launcher notif_dump_tf_placeholder) */ static void dump_tf_wrapper(const meow_notif_t *n){ (void)meow_notif_dump_tf(n); } /* --- 1) CSV 转义 + 去换行 --- */ static void test_csv_escape(const char *path) { meow_notif_set_tf_path(path); meow_notif_set_tf_max(1024 * 1024); /* 放大上限避免此测触发截断 */ meow_notif_t n; /* summary: 逗号 + 双引号 + 换行;body: CRLF + tab */ make_notif(&n, 42, "Hel\"lo, world\nline2", "Body\r\nwith\ttab"); n.media = 1; /* 让 media 列=1 */ CHECK(meow_notif_dump_tf(&n) == 0, "dump_tf returns 0"); char *s = read_all(path, NULL); CHECK(s != NULL, "file exists after dump"); const char *expect = "1234567890123,42,\"com.app\",\"\",\"general\",0,-1,\"Hel\"\"lo, world line2\"," "\"Body with\ttab\",\"act\",1,1,0\n"; /* app_icon="" urgency=0 progress=-1 ongoing=1 media=1 dismissed=0 */ CHECK(s && strcmp(s, expect) == 0, "CSV line matches expected (escape+newline-strip+new cols)"); if (s) { fprintf(stderr, " got: %s", s); fprintf(stderr, " exp: %s", expect); } free(s); } /* --- 2) 10MB 滚动截断(假文件:把上限压到很小模拟)--- */ static void test_trim(const char *path) { meow_notif_set_tf_path(path); meow_notif_set_tf_max(256); /* 用 256B 模拟 10MB 上限 */ /* 先写 4 条不同标记,让文件超过 256B */ meow_notif_t n; make_notif(&n, 1, "lineAAAA", ""); meow_notif_dump_tf(&n); make_notif(&n, 2, "lineBBBB", ""); meow_notif_dump_tf(&n); make_notif(&n, 3, "lineCCCC", ""); meow_notif_dump_tf(&n); make_notif(&n, 4, "lineDDDD", ""); meow_notif_dump_tf(&n); long len = 0; char *s = read_all(path, &len); CHECK(s != NULL && len <= 256, "after roll, file size <= limit (10MB analog)"); CHECK(s && strstr(s, "lineDDDD") != NULL, "newest row retained after roll"); CHECK(s && strstr(s, "lineAAAA") == NULL, "oldest row rolled away"); if (s) fprintf(stderr, " rolled content (%ldB):\n%s", len, s); /* 追加一条仍 0 成功 */ make_notif(&n, 5, "lineEEEE", ""); CHECK(meow_notif_dump_tf(&n) == 0, "append after roll ok"); free(s); } /* --- 3) init 清板载 + 清内存表,且 post 正常 --- */ static void test_init_clear(const char *path) { meow_notif_set_tf_path(path); meow_notif_set_tf_max(1024 * 1024); /* 先塞点板载内容 */ meow_notif_t n; make_notif(&n, 99, "junk", ""); meow_notif_dump_tf(&n); /* init:应清板载文件 + 清内存表 */ CHECK(meow_notif_init() == NOTIF_OK, "meow_notif_init ok"); long len = -1; char *s = read_all(path, &len); CHECK(s != NULL && len == 0, "board log cleared at init (dir kept)"); CHECK(meow_notif_count(NULL) == 0, "memory table cleared at init"); /* init 后 post 一条,应活跃 + 可转储 */ make_notif(&n, 0, "fresh", ""); uint32_t id = 0; CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "post after init ok"); CHECK(id != 0 && meow_notif_count(NULL) == 1, "one active notif after post"); free(s); } /* --- 4) icon 回退:app_icon 空 → 按 app_id 用该 app 默认图标;非空 → 保留 --- */ static void test_icon_fallback(const char *path) { meow_notif_set_icon_cb(stub_icon_cb); meow_notif_before_poweroff(); /* 清内存表,回到干净态(dump_cb=NULL 不落盘) */ meow_notif_set_tf_path(path); meow_notif_set_tf_max(1024 * 1024); /* 空 app_icon → 回退 app 图标 */ meow_notif_t n; make_notif(&n, 0, "icon-test", ""); uint32_t id = 0; CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "icon-fallback post ok"); meow_notif_t got = find_notif("icon-test"); CHECK(got.id != 0 && !strcmp(got.app_icon, "app_icon_name"), "empty app_icon → app icon fallback"); /* 非空 app_icon → 独立图标(不回退) */ make_notif(&n, 0, "icon-custom", ""); n.app_icon[0] = 0; strcpy(n.app_icon, "custom/icon.png"); CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "icon-custom post ok"); got = find_notif("icon-custom"); CHECK(got.id != 0 && !strcmp(got.app_icon, "custom/icon.png"), "non-empty app_icon kept (per-notif icon)"); } /* --- 5) progress 实时(仅内存)vs 里程碑(100% 落盘 + 释放) --- */ static void test_progress_live(const char *path) { meow_notif_set_icon_cb(NULL); meow_notif_set_dump_cb(NULL); meow_notif_before_poweroff(); /* 清内存表,回到干净态 */ meow_notif_set_tf_path(path); meow_notif_set_tf_max(1024 * 1024); meow_notif_set_dump_cb(dump_tf_wrapper); /* 归档即写 CSV;验证落盘快照 */ meow_notif_t n; make_notif(&n, 0, "live", ""); n.ongoing = 0; /* 非 ONGOING,便于验证 100% 释放 */ n.progress = 0; uint32_t id = 0; CHECK(meow_notif_post(&n, &id) == NOTIF_OK, "progress post ok"); CHECK(meow_notif_count(NULL) == 1, "one active after post"); /* 实时 <100%:仅内存,不落盘 */ CHECK(meow_notif_update_progress(id, 70) == NOTIF_OK, "update_progress(70) ok"); CHECK(meow_notif_count(NULL) == 1, "still active after live update (not dumped)"); long len = 0; char *s = read_all(path, &len); CHECK((s == NULL || len == 0), "no CSV written on live update (<100%)"); free(s); /* 里程碑 100%:立即归档落盘 + 释放非 ONGOING 槽 */ CHECK(meow_notif_update_progress(id, 100) == NOTIF_OK, "update_progress(100) ok"); CHECK(meow_notif_count(NULL) == 0, "slot released after 100% milestone"); s = read_all(path, &len); CHECK(s != NULL && len > 0, "CSV written on 100% milestone"); CHECK(s && strstr(s, ",100,\"live\"") != NULL, "milestone CSV has progress=100 title=live"); if (s) fprintf(stderr, " milestone row: %s", s); free(s); /* 已释放 id → NOENT */ CHECK(meow_notif_update_progress(id, 50) == NOTIF_ERR_NOENT, "update on released id -> NOENT"); meow_notif_set_dump_cb(NULL); /* 复位 */ } int main(void) { char tmp[] = "/tmp/meow_notif_tf_test_XXXXXX"; char *dir = mkdtemp(tmp); if (!dir) { perror("mkdtemp"); return 2; } char p1[512], p2[512], p3[512], p4[512], p5[512]; snprintf(p1, sizeof p1, "%s/csv.log", dir); snprintf(p2, sizeof p2, "%s/trim.log", dir); snprintf(p3, sizeof p3, "%s/init.log", dir); snprintf(p4, sizeof p4, "%s/icon.log", dir); snprintf(p5, sizeof p5, "%s/prog.log", dir); CHECK(p1[0] != 0, "tmpdir ready"); test_csv_escape(p1); test_trim(p2); test_init_clear(p3); test_icon_fallback(p4); test_progress_live(p5); /* 清理:删除日志文件 + 目录(保底) */ unlink(p1); unlink(p2); unlink(p3); unlink(p4); unlink(p5); rmdir(dir); fprintf(stderr, "\n%s (failures=%d)\n", g_fail ? "== TEST FAILED ==" : "== TEST PASSED ==", g_fail); return g_fail ? 1 : 0; }