Files
MeowOS-SDK/tuku/test/test_browse_perf.c
T
AskaEth 4befbfe14c 图库: 书库点图改按文件名定位(修 P1 截断错位)+ 册间衔接根锁定(M18 评审 r1)
P1-1(会静默开错图):取消「书库从列表推导图片下标」。
- 新增 mr_reader_open_dir_at_name(dir, name):阅读器打开目录册后,在**自己刚枚举出的
  全量目录册**(mr_dir_book,与翻页同一份枚举)里按 basename 定位起始页(先精确,
  再大小写不敏感兜底;找不到退回续读/首页)。书库点击只传文件名。
- 删除 mr_library_image_index(不再有跨枚举的隐式契约)。
- 截断提示文案改为「仅显示前 2000 项(图片可在阅读器中翻到)」。
- 修复前(cb02722 探针,2100 张 4 位名夹具):rows=2000/truncated=1/
  library_images=2000/reader_pages=2100/**mismatched=590**(首行 p1412 → 打开 p1411);
  评审夹具为 1995/2000。修复后:2000 个可见图片行按名定位 **0 mismatch**,
  2100 页全量可翻;26 次真实 UI 点开(首尾各 3 + 20 个确定性伪随机)逐张命中。

连带处理(评审 r1):
1. 删掉 reader_page_enter 的 c->view 死写(reopen 判定用 activity active/depth);
   顺手删掉 library_page_create 里同款死写。
2. 阅读器册间衔接根锁定:新增 mr_path_within_root + mr_book_adjacent_rooted,
   book_switch 用浏览根(默认 MR_ROOT_DIR,测试可注入)钳制相邻册;
   根外的兄弟册按「没有相邻册」处理。test_cont_adjacent A7 验证「明文 _ex 会越根、
   rooted 拒绝、根内翻册不受影响」;test_mode_switch_host H6 验证条漫末页不越根。
3. test_reader_perf 不再依赖顶栏 tap 命中时序:模式切换改走测试钩子
   mr_reader_test_toggle_mode()(与顶栏动作同一个 mode_set),负载下稳定。
4. page_cache 注释去掉「缩略图缓存/缩略图线程」过时描述(只改文字)。

回归:run_tests.sh(library 81 / nav 74 / lazy 57 / perf 25 / … / open_book 含 2100 用例)
与 run_webtoon_tests.sh(含 A7/H6)全 PASS;交叉构建零警告。
2026-11-05 20:26:40 +08:00

222 lines
8.6 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");
}
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;
}