Files
MeowOS-SDK/tuku/test/test_browse_perf.c
T
AskaEth 64336f95d9 图库: 书库改文件管理器模型 + 关闭缩略图 + 根锁定/无卡容错(M18)
用户反馈四件事一起改:
- 「目录里有一张图就被当成最终目录」→ 取消「目录含图=一本书」全部链路
  (探测/页数/封面/续读角标/懒算 worker/sr2 补位),列表 = 目录 + 图片 +
  .cbz/.zip(目录优先、组内自然序)。点目录=进入(空目录也进),点图片=
  mr_reader_open_dir_at(当前目录, 图片自然序下标),点档案册=打开档案册。
- 「文件夹浏览特别卡」→ 删除缩略图线程/磁盘 bmp 缓存/LRU/解码路径与行内
  缩略图;首帧只做一次 opendir/readdir+lstat,副标题(类型·大小)扫描期
  生成一次,行合成/滚动/点击零 IO 零格式化;去掉逐行 snap(自由滚动手感)。
  2000 项目录扫描 ~2.8ms(opendir=1, lstat=2000),行合成 2.78ms/200 万次
  (宿主实测;旧实现 2000 项 entry 含 512B cover,内存 2.7MB→1.7MB)。
- 「不要访问 SD 卡上级目录」→ 浏览根锁定 MR_ROOT_DIR:删除越根的顶栏
  「上级」/library_go_parent,根隐藏顶栏返回,左缘返回到根=退出 App。
- 「没有 SD 卡不要报错」→ 根不可访问(未插卡/挂载失败/根是文件)时友好空态
  「未检测到 SD 卡」+ 顶栏刷新/重进页面重试;子目录不可读才弹提示横幅。

集成陷阱:书库图片下标与阅读器目录册共享同一套过滤器(隐藏项/图片扩展名/
普通文件/符号链接)与 mr_natural_cmp,test_browse_lazy 直接对比
mr_dir_book_init 顺序,test_open_book_host 断言「点 img10 就开 img10」。

reader 侧 M17 遗留:mr_reader_open_dir_at 在阅读页已激活时先 pop 再 push,
走完整 leave/enter(SDK 对同 view 重复 push 是 no-op 且会压重复 activity)。

回归:run_tests.sh / run_webtoon_tests.sh 全 PASS;交叉构建零警告。
2026-11-05 20:26:40 +08:00

224 lines
8.7 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_browse_perf.c — 单层扫描 / 行合成性能与有界性验收(纯 C,无 SDK)。
*
* 夹具:500 个顶层目录 × 8 层嵌套(4000 个子目录)+ 根目录 2000 个普通文件
* + 一个 big/ 目录(2000 张图片,验证 2000 项大目录)。
* 断言(对应 M18 任务③):
* P1 首帧扫描只做单层:opendir 次数 == 1,lstat 次数 == 本层条目数,耗时有界;
* P2 2000 项大目录:一次扫描出全部 2000 行,opendir=1、lstat=2000,耗时有界;
* P3 行合成(UI src 的数据面)纯内存、零 IO、耗时有界(2000 行 × 1000 遍);
* P4 进入深层/普通子目录同样只打开它自己(进入目录成本 = 该目录一层)。
*
* 编译(在 tuku/ 下):
* gcc -Wall -Wextra -O1 -I. -I../sdk -DMR_LIBRARY_TEST_HOOKS \
* test/test_browse_perf.c logic/library.c -pthread -o build/host/test_browse_perf
*/
#include "logic/library.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define PERF_DIRS 500
#define PERF_DEPTH 8
#define PERF_FILES 2000
#define PERF_BIG 2000
static int g_pass, g_fail;
#define CHECK(cond, ...) do { \
if (cond) { g_pass++; } \
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
} while (0)
static int mkdir_p(const char *path)
{
char tmp[MR_PATH_MAX];
snprintf(tmp, sizeof tmp, "%s", path);
for (size_t i = 1; i <= strlen(tmp); i++) {
if (tmp[i] == '/' || tmp[i] == '\0') {
char ch = tmp[i];
tmp[i] = '\0';
if (mkdir(tmp, 0755) != 0) {
struct stat st;
if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)) return -1;
}
tmp[i] = ch;
}
}
return 0;
}
static int touch_file(const char *path)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs("x", f);
return fclose(f);
}
static int rm_rf(const char *path)
{
struct stat st;
if (lstat(path, &st) != 0) return -1;
if (!S_ISDIR(st.st_mode)) return unlink(path);
DIR *d = opendir(path);
if (!d) return -1;
struct dirent *de;
while ((de = readdir(d)) != NULL) {
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue;
char full[MR_PATH_MAX];
snprintf(full, sizeof full, "%s/%s", path, de->d_name);
rm_rf(full);
}
closedir(d);
return rmdir(path);
}
static long long now_us(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL;
}
int main(void)
{
char tmpl[] = "./mr_perf_XXXXXX";
char *root = mkdtemp(tmpl);
CHECK(root != NULL, "mkdtemp");
if (!root) return 1;
char p[MR_PATH_MAX];
/* 根目录:2000 个普通文件(不成行)+ 500 个顶层目录(每个顶层目录内是 8 层嵌套 + 1 张图)。 */
for (int f = 0; f < PERF_FILES; f++) {
snprintf(p, sizeof p, "%s/f%04d.txt", root, f);
if (touch_file(p) != 0) { CHECK(0, "file %s", p); break; }
}
for (int d = 0; d < PERF_DIRS; d++) {
char deep[MR_PATH_MAX];
snprintf(deep, sizeof deep, "%s/d%04d", root, d);
if (mkdir_p(deep) != 0) { CHECK(0, "mkdir %s", deep); break; }
snprintf(p, sizeof p, "%s/001.png", deep);
if (touch_file(p) != 0) { CHECK(0, "touched top image"); break; }
for (int l = 1; l <= PERF_DEPTH; l++) {
char sub[MR_PATH_MAX];
snprintf(sub, sizeof sub, "%s/l%d", deep, l);
if (mkdir(sub, 0755) != 0) { CHECK(0, "mkdir %s", sub); break; }
snprintf(deep, sizeof deep, "%s", sub);
}
snprintf(p, sizeof p, "%s/001.png", deep);
if (touch_file(p) != 0) { CHECK(0, "touched deep image"); break; }
}
/* 2000 项大目录(图片)。 */
char big[MR_PATH_MAX];
snprintf(big, sizeof big, "%s/big", root);
CHECK(mkdir_p(big) == 0, "mkdir big");
for (int i = 0; i < PERF_BIG; i++) {
snprintf(p, sizeof p, "%s/img%04d.png", big, i);
if (touch_file(p) != 0) { CHECK(0, "big image %s", p); break; }
}
/* ---- P1:首帧扫描只做单层 ---- */
mr_library lib;
memset(&lib, 0, sizeof lib);
mr_library_test_io_reset();
long long t0 = now_us();
int n = mr_library_scan(&lib, root, MR_LIB_MAX_DEPTH);
long long scan_us = now_us() - t0;
mr_library_io_stats st;
mr_library_test_io_get(&st);
CHECK(n == PERF_DIRS + 1, "scan lists %d dirs (got %d)", PERF_DIRS + 1, n);
CHECK(lib.truncated == 0, "not truncated");
CHECK(st.opendirs == 1, "single-layer scan: opendir=%ld (want 1)", st.opendirs);
CHECK(st.lstats == PERF_FILES + PERF_DIRS + 1,
"lstat only current level: %ld (want %d)", st.lstats, PERF_FILES + PERF_DIRS + 1);
CHECK(scan_us < 1500000LL, "scan bounded: %lldus", scan_us);
if (lib.count == PERF_DIRS + 1) {
CHECK(strcmp(lib.items[0].name, "big") == 0, "first=%s", lib.items[0].name);
CHECK(strcmp(lib.items[lib.count - 1].name, "d0499") == 0,
"last=%s", lib.items[lib.count - 1].name);
}
printf(" root scan: %lldus, %d rows, opendir=%ld lstat=%ld\n",
scan_us, n, st.opendirs, st.lstats);
mr_library_free(&lib);
/* ---- P2:2000 项大目录(进入目录 = 单层扫描) ---- */
mr_library biglib;
memset(&biglib, 0, sizeof biglib);
mr_library_test_io_reset();
long long b0 = now_us();
int bn = mr_library_scan(&biglib, big, MR_LIB_MAX_DEPTH);
long long big_us = now_us() - b0;
mr_library_test_io_get(&st);
CHECK(bn == PERF_BIG, "big dir rows=%d (want %d)", bn, PERF_BIG);
CHECK(biglib.truncated == 0, "big dir not truncated at cap");
CHECK(st.opendirs == 1, "big dir scan: opendir=%ld", st.opendirs);
CHECK(st.lstats == PERF_BIG, "big dir scan: lstat=%ld (want %d)", st.lstats, PERF_BIG);
CHECK(big_us < 1500000LL, "big dir scan bounded: %lldus", big_us);
if (biglib.count == PERF_BIG) {
CHECK(strcmp(biglib.items[0].name, "img0000.png") == 0, "first=%s", biglib.items[0].name);
CHECK(strcmp(biglib.items[PERF_BIG - 1].name, "img1999.png") == 0,
"last=%s", biglib.items[PERF_BIG - 1].name);
CHECK(biglib.items[PERF_BIG - 1].kind == MR_LIB_KIND_IMAGE, "last is image");
CHECK(mr_library_image_index(&biglib, PERF_BIG - 1) == PERF_BIG - 1,
"image index at end");
}
printf(" big dir scan: %lldus, %d rows, opendir=%ld lstat=%ld\n",
big_us, bn, st.opendirs, st.lstats);
/* ---- P3:行合成(UI src 数据面)纯内存、2000 行 × 1000 遍 ---- */
mr_library_test_io_reset();
long long r0 = now_us();
int rows = 0;
for (int r = 0; r < 1000; r++)
for (int i = 0; i < biglib.count; i++) {
mr_library_row row;
if (mr_library_row_at(&biglib, i, &row)) rows++;
}
long long row_us = now_us() - r0;
mr_library_test_io_get(&st);
CHECK(rows == 1000 * biglib.count, "rows synthesized (%d)", rows);
CHECK(st.opendirs == 0 && st.readdirs == 0 && st.lstats == 0,
"row synthesis does no IO (open=%ld rd=%ld stat=%ld)",
st.opendirs, st.readdirs, st.lstats);
CHECK(row_us < 1000000LL, "row synthesis bounded: %lldus", row_us);
printf(" rows: %lldus for %d rows, zero IO\n", row_us, rows);
mr_library_free(&biglib);
/* ---- P4:进入子目录只打开它自己(不递归子树) ---- */
char deep[MR_PATH_MAX];
snprintf(deep, sizeof deep, "%s/d0000", root);
for (int l = 1; l <= PERF_DEPTH; l++) {
char sub[MR_PATH_MAX];
snprintf(sub, sizeof sub, "%s/l%d", deep, l);
snprintf(deep, sizeof deep, "%s", sub);
}
mr_library_test_io_reset();
mr_library deep_lib;
memset(&deep_lib, 0, sizeof deep_lib);
CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 1, "deep leaf has one image");
mr_library_test_io_get(&st);
CHECK(st.opendirs == 1, "deep scan opens once (got %ld)", st.opendirs);
CHECK(st.lstats == 1, "deep scan lstats only its image (got %ld)", st.lstats);
mr_library_free(&deep_lib);
/* 顶层目录:一个子目录 + 一张图 = 2 行,只打开它自己。 */
snprintf(deep, sizeof deep, "%s/d0000", root);
memset(&deep_lib, 0, sizeof deep_lib);
mr_library_test_io_reset();
CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 2, "top dir rows");
mr_library_test_io_get(&st);
CHECK(st.opendirs == 1 && st.lstats == 2, "top dir scan: open=%ld stat=%ld",
st.opendirs, st.lstats);
mr_library_free(&deep_lib);
rm_rf(root);
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
return g_fail ? 1 : 0;
}