Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_notif_panel.c
T
MeowOS 2b17f69f24 阶段B 通知中心UI(agent-123) + 进程模型/文档完整(agent-124) + 对外README(agent-125)
- 通知中心=launcher侧系统级叠加层(§6.1③ 仅内部): meow_notif_panel.h(纯逻辑) + meow_launcherd.c(内联 draw/run_notif_center + run_menu顶部热区下滑接入) + test_notif_panel.c(56单测)
- 通知中心零旁路走 meow_settings_*; 通知列表读 meow_notif; 左缘返回复用 meow_edge_back; 局部脏区; 系统级控件无公共API(App无法自建悬浮层)
- meow_ui.a 零改动(d249c6e6); meow_launcherd=4652901a(0warn)
- FRAMEWORK_DESIGN §10 进程模型/Supervisor树(svcd服务子进程化隔离防级联崩) ; MEOW_APP_SDK §6.1 系统UI归launcher+SDK/APP进程隔离; NOTIFICATION_SYSTEM §9 归属; SYSTEM_SETTINGS/SPEC_REF/DEVELOPMENT_PLAN 补齐
- README 重写为面向第三方(进度/架构/构建/部署/SDK开发; 固件打包标注本地工具不入库, 无敏感)
- 遗留: SDK(App进程)侧通知中心/activity跳转 依赖 M3 导航栈(独立里程碑); 真机待验证
2026-08-30 15:59:23 +08:00

237 lines
11 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_notif_panel.c — 宿主单测:阶段B 通知中心面板(纯逻辑 + 面板读取的存储契约)。
*
* 直接 #include "meow_notif.c" + "meow_settings.c" 于同一 TU(同 test_notif_tf.c 手法),
* 以「真实 meow_notif 存储 + 真实 meow_settings 后端」验证面板真正读取的字段契约;
* 再测 meow_notif_panel.h 的纯逻辑(滑条映射/亮度 clamp/顶部下滑判定/时间显示/行命中/滚动钳位)。
* 不依赖 launcher 其它 static / fb / 触摸。
*
* 运行:gcc -D_GNU_SOURCE -o test_notif_panel test_notif_panel.c && ./test_notif_panel
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
/* stub:meow_notif.c 的 read_quota() 经 meow_settings_get_int 读 notify.* 配额。
* <0 => 键缺失 => 用默认(MEOW_NOTIF_CATS_DEF=4 / CAT_QUOTA_DEF=4 / MEDIA_QUOTA_DEF=1)。
* meow_notif.c 对 meow_settings 的唯一依赖就是这一个函数。 */
int meow_settings_get_int(const char *key, int *out)
{
(void)key; (void)out;
return -2; /* MEOW_ERR_NOENT */
}
/* 先把 tf 转储路径指向临时目录,避免污染真实卡路径(meow_notif_init 会 clear tf)*/
static char g_tflog[600];
#include "meow_notif.c" /* 真实通知存储:post/list/count/dismiss/update_progress/... */
#include "meow_notif_panel.h" /* 面板纯逻辑(header-only) */
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)
/* 收集列表到固定槽(回调式,模拟面板 ncp_collect_cb 的字段读取) */
#define MAXG 16
typedef struct { uint32_t id; char summary[96]; char body[96]; char icon[64];
char app[48]; char tgt[48]; int progress; uint8_t urgency; uint64_t ms; } gslot_t;
static gslot_t g_got[MAXG]; static int g_nslots = 0;
static int list_grab_cb(const meow_notif_t *n, void *ctx)
{
(void)ctx;
if (g_nslots >= MAXG) return 1;
gslot_t *s = &g_got[g_nslots++];
memset(s, 0, sizeof *s);
s->id = n->id;
snprintf(s->summary, sizeof s->summary, "%s", n->summary);
snprintf(s->body, sizeof s->body, "%s", n->body);
snprintf(s->icon, sizeof s->icon, "%s", n->app_icon);
snprintf(s->app, sizeof s->app, "%s", n->app_id);
snprintf(s->tgt, sizeof s->tgt, "%s", n->target_activity);
s->progress = n->progress;
s->urgency = n->urgency;
s->ms = n->created_ms;
return 0;
}
static int grab_list(void)
{
g_nslots = 0;
meow_notif_list(NULL, list_grab_cb, NULL);
return g_nslots;
}
static gslot_t *find_slot(const char *summary)
{
for (int i = 0; i < g_nslots; i++)
if (!strcmp(g_got[i].summary, summary)) return &g_got[i];
return NULL;
}
static uint64_t wall_now_ms(void)
{
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
static void test_pure_logic(void)
{
fprintf(stderr, "--- P1 pure logic ---\n");
/* mnp_top_swipe_down */
CHECK(mnp_top_swipe_down(40, 5, 3, 90, 28, 65) == 1, "top hotzone down-dominant -> open");
CHECK(mnp_top_swipe_down(10, 5, 3, 90, 28, 65) == 0, "left-edge (down_x<24) reserved -> no open");
CHECK(mnp_top_swipe_down(40, 30, 3, 90, 28, 65) == 0, "origin below hotzone -> no open");
CHECK(mnp_top_swipe_down(40, 5, 3, 40, 28, 65) == 0, "below swipe threshold -> no open");
CHECK(mnp_top_swipe_down(40, 5, 100, 70, 28, 65) == 0, "horizontal-dominant -> no open");
CHECK(mnp_top_swipe_down(40, 5, -3, 90, 28, 65) == 1, "down with minor left drift still opens");
/* mnp_slider_val_from_x */
CHECK(mnp_slider_val_from_x(20, 20, 440, 1, 100) == 1, "slider x=left -> min");
CHECK(mnp_slider_val_from_x(460, 20, 440, 1, 100) == 100, "slider x=right -> max");
int mid = mnp_slider_val_from_x(240, 20, 440, 1, 100);
CHECK(mid >= 1 && mid <= 100, "slider x=mid within [min,max]");
CHECK(mnp_slider_val_from_x(0, 20, 440, 1, 100) == 1, "slider x out-left clamped to min");
CHECK(mnp_slider_val_from_x(999, 20, 440, 1, 100) == 100, "slider x out-right clamped to max");
/* mnp_brightness_clamp */
CHECK(mnp_brightness_clamp(0, 1) == 1, "brightness clamp below min -> min");
CHECK(mnp_brightness_clamp(150, 1) == 100, "brightness clamp above 100 -> 100");
CHECK(mnp_brightness_clamp(50, 1) == 50, "brightness 50 kept");
CHECK(mnp_brightness_clamp(3, 5) == 5, "brightness below custom min -> custom min");
CHECK(mnp_brightness_clamp(-7, -3) == 1, "brightness clamp forces vmin>=1");
/* mnp_time_ago */
uint64_t now = wall_now_ms();
char tb[32];
mnp_time_ago(now - 5000, now, tb, sizeof tb);
CHECK(!strcmp(tb, "刚刚"), "time_ago <60s -> 刚刚");
mnp_time_ago(now - 5 * 60000u, now, tb, sizeof tb);
CHECK(!strcmp(tb, "5分钟前"), "time_ago 5min -> 5分钟前");
mnp_time_ago(now - 3 * 3600000u, now, tb, sizeof tb);
CHECK(strlen(tb) == 5 && tb[2] == ':', "time_ago >1h -> HH:MM");
/* mnp_row_at */
CHECK(mnp_row_at(250, 252, 696, 92) == -1, "row_at above list -> -1");
CHECK(mnp_row_at(300, 252, 696, 92) == 0, "row_at first row -> 0");
CHECK(mnp_row_at(400, 252, 696, 92) == 1, "row_at second row -> 1");
CHECK(mnp_row_at(700, 252, 696, 92) == -1, "row_at below list -> -1");
/* mnp_scroll_clamp */
CHECK(mnp_scroll_clamp(0, 2000, 444) == 0, "scroll clamp lower -> 0");
CHECK(mnp_scroll_clamp(9999, 2000, 444) == 1556, "scroll clamp upper -> total-view");
CHECK(mnp_scroll_clamp(-5, 2000, 444) == 0, "scroll clamp negative -> 0");
CHECK(mnp_scroll_clamp(5, 400, 444) == 0, "content fits view -> 0");
}
static void test_storage_contract(void)
{
fprintf(stderr, "--- P0 storage contract (panel reads) ---\n");
/* meow_notif_init:应清内存表 + 清 tf 文件(路径已指临时);配额用默认(stub 返回 NOENT) */
CHECK(meow_notif_init() == NOTIF_OK, "meow_notif_init ok");
CHECK(meow_notif_count(NULL) == 0, "init clears memory table");
CHECK(g_max_cats == 4 && g_cat_quota == 4 && g_media_quota == 1,
"quota defaults from meow_settings backend (stub NOENT -> defaults)");
meow_notif_t n; memset(&n, 0, sizeof n);
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
snprintf(n.category, sizeof n.category, "general");
snprintf(n.summary, sizeof n.summary, "你好世界");
snprintf(n.body, sizeof n.body, "这是正文第二行");
snprintf(n.app_icon, sizeof n.app_icon, "apps/os.meow.hello/icon.png");
snprintf(n.target_activity, sizeof n.target_activity, "activity_main");
n.expire_timeout = 0; n.progress = -1; n.urgency = 1;
uint32_t id1 = 0;
CHECK(meow_notif_post(&n, &id1) == NOTIF_OK, "post general ok");
CHECK(id1 != 0, "post assigns id");
/* 媒体通知:ongoing + 进度 */
memset(&n, 0, sizeof n);
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
snprintf(n.category, sizeof n.category, "media");
snprintf(n.summary, sizeof n.summary, "正在播放");
n.expire_timeout = 0xFFFFFFFFu; n.ongoing = 1; n.progress = 20; n.urgency = 2;
uint32_t id2 = 0;
CHECK(meow_notif_post(&n, &id2) == NOTIF_OK, "post media ok");
/* 高紧急 + 进度 */
memset(&n, 0, sizeof n);
snprintf(n.app_id, sizeof n.app_id, "system");
snprintf(n.category, sizeof n.category, "event");
snprintf(n.summary, sizeof n.summary, "系统提醒");
snprintf(n.body, sizeof n.body, "电量低");
n.progress = 50; n.urgency = 3;
uint32_t id3 = 0;
CHECK(meow_notif_post(&n, &id3) == NOTIF_OK, "post event(high) ok");
CHECK(meow_notif_count(NULL) == 3, "count == 3 after posts");
/* 遍历(面板 ncp_collect_cb 同款字段读取)*/
int got = grab_list();
CHECK(got == 3, "list returns 3 active notifs");
gslot_t *s1 = find_slot("你好世界");
CHECK(s1 != NULL, "panel sees summary 你好世界");
if (s1) {
CHECK(!strcmp(s1->app, "os.meow.hello"), "app_id");
CHECK(!strcmp(s1->body, "这是正文第二行"), "body");
CHECK(!strcmp(s1->icon, "apps/os.meow.hello/icon.png"), "app_icon");
CHECK(!strcmp(s1->tgt, "activity_main"), "target_activity");
CHECK(s1->progress == -1, "general progress=-1 (none)");
CHECK(s1->urgency == 1, "general urgency=NORMAL(1)");
CHECK(s1->ms > 0, "created_ms set");
}
gslot_t *s2 = find_slot("正在播放");
CHECK(s2 != NULL && s2->progress == 20 && s2->urgency == 2, "media progress=20 urgency=HIGH(2)");
gslot_t *s3 = find_slot("系统提醒");
CHECK(s3 != NULL && s3->progress == 50 && s3->urgency == 3, "event progress=50 urgency=CRITICAL(3)");
/* dismiss → 面板列表减一(点通知=阅读)*/
CHECK(meow_notif_dismiss(id1) == NOTIF_OK, "dismiss general ok");
CHECK(meow_notif_count(NULL) == 2, "count == 2 after dismiss");
grab_list();
CHECK(find_slot("你好世界") == NULL, "dismissed notif no longer listed");
/* 里程碑 100% → 释放(非 ONGOING):面板列表再减一 */
CHECK(meow_notif_update_progress(id3, 100) == NOTIF_OK, "update_progress(100) ok");
CHECK(meow_notif_count(NULL) == 1, "milestone released event slot -> count 1");
grab_list();
CHECK(find_slot("系统提醒") == NULL, "100% milestone notif no longer listed");
/* 剩余媒体通知仍活跃(ONGOING 常驻)*/
CHECK(find_slot("正在播放") != NULL, "ongoing media stays listed");
/* 配额:单 app 媒体单例(media_quota=1)——再发一条 media 顶掉旧媒体 */
memset(&n, 0, sizeof n);
snprintf(n.app_id, sizeof n.app_id, "os.meow.hello");
snprintf(n.category, sizeof n.category, "media");
snprintf(n.summary, sizeof n.summary, "下一首");
n.expire_timeout = 0xFFFFFFFFu; n.ongoing = 1; n.progress = 60;
uint32_t id4 = 0;
CHECK(meow_notif_post(&n, &id4) == NOTIF_OK, "post second media ok");
CHECK(meow_notif_count(NULL) == 1, "media quota=1 -> old media replaced, count stays 1");
grab_list();
CHECK(find_slot("下一首") != NULL && find_slot("正在播放") == NULL,
"media singleton replaced oldest");
}
int main(void)
{
char tmp[] = "/tmp/meow_notif_panel_XXXXXX";
char *dir = mkdtemp(tmp);
if (!dir) { perror("mkdtemp"); return 2; }
snprintf(g_tflog, sizeof g_tflog, "%s/notif.log", dir);
meow_notif_set_tf_path(g_tflog);
meow_notif_set_tf_max(1024 * 1024);
test_pure_logic();
test_storage_contract();
unlink(g_tflog); rmdir(dir);
fprintf(stderr, "\n%s (failures=%d)\n", g_fail ? "== TEST FAILED ==" : "== TEST PASSED ==", g_fail);
return g_fail ? 1 : 0;
}