Files

156 lines
6.8 KiB
Python
Raw Permalink 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_font_weight.py — MeowOS 字重结构(M2.6)宿主导出单测。
验证 launcher 侧 font 扫描/解析的两块(MEOWOS_SPEC_REF §5.2 字重结构 + §5.7.1 扫描):
(1) 两级扫描:<卡根>/MeowOS/fonts/<family>/<weight>/ 每 (family,weight) 一套,生成
preset(值 = MeowOS/fonts/<family>/<weight>/font.png,fam/wt 单列)。
(2) font.json weight 解析:label = `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
缺 weight 回退 regular;缺三件套/损坏 → 跳过;旧式一级布局(font.png 直接在 <family>/)
→ 不产出(迁移后需重扫)。
方法:构造一个假卡根(含合法多 weight family / 缺件 family / 旧式文件 / 顶层 stray),
用 qemu-mipsel 跑真实 meow_launcherd --fontscan <root>,断言 stdout 的 preset 列表。
用法(在 patch_v2/meow_launcherd 下):
先 build_launcher.sh 生成 meow_launcherd(mipsel 静态 ELF),再:
python3 test_font_weight.py
依赖 qemu-mipsel(qemu-user)。退出码 0=全过,非 0=断言失败。
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
ROOT = os.path.dirname(os.path.abspath(__file__))
BIN = os.path.join(ROOT, "meow_launcherd")
FIX_ROOT = tempfile.mkdtemp(prefix="mfscan_", dir="/tmp")
FONTS = os.path.join(FIX_ROOT, "MeowOS", "fonts")
def write_font(fam, wt, name, weight="regular", charset="common"):
"""写一套 <fam>/<wt>/font.png+font.idx+font.json(合法三件套;png/idx 为占位字节)。"""
d = os.path.join(FONTS, fam, wt)
os.makedirs(d, exist_ok=True)
with open(os.path.join(d, "font.png"), "wb") as f:
f.write(b"\x89PNG\r\n\x1a\nPLACEHOLDER")
with open(os.path.join(d, "font.idx"), "wb") as f:
f.write(b"MFNI\x02\x00PLACEHOLDER")
meta = {
"family": fam, "name": name, "weight": weight, "glyph_count": 4110,
"charset": charset, "size_list": [20, 28], "bit_depth": 4,
"sha256_png": "0" * 64, "sha256_idx": "0" * 64,
}
with open(os.path.join(d, "font.json"), "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
def run_scan():
"""返回 (ret, stdout)。"""
r = subprocess.run(["qemu-mipsel", BIN, "--fontscan", FIX_ROOT],
capture_output=True, text=True, timeout=120)
return r.returncode, r.stdout
def main():
if not os.path.exists(BIN):
print(f"FAIL: 未找到 {BIN},请先跑 build_launcher.sh"); return 2
if shutil.which("qemu-mipsel") is None:
print("FAIL: 缺 qemu-mipsel"); return 2
# ---- 构造卡根 ----
write_font("misans", "regular", "MiSans", weight="regular")
write_font("misans", "bold", "MiSans", weight="bold")
write_font("meslo", "regular", "Meslo LG S Mono", weight="regular")
write_font("default", "regular", "font", weight="regular")
# 缺件:无 font.idx → 应跳过
broken = os.path.join(FONTS, "broken", "regular")
os.makedirs(broken, exist_ok=True)
with open(os.path.join(broken, "font.png"), "wb") as f:
f.write(b"PNG")
with open(os.path.join(broken, "font.json"), "w") as f:
json.dump({"family": "broken", "name": "Broken", "weight": "regular"}, f)
# 旧式一级布局:font.png 直接在 <family>/ 下(无 weight 级)→ 迁移后应不产出
old = os.path.join(FONTS, "legacy")
os.makedirs(old, exist_ok=True)
with open(os.path.join(old, "font.png"), "wb") as f:
f.write(b"PNG")
# 顶层 stray 文件(非目录)→ 应忽略
with open(os.path.join(FONTS, "stray.png"), "wb") as f:
f.write(b"PNG")
rc, out = run_scan()
if rc != 0:
print(f"FAIL: qemu --fontscan 返回 {rc}\n{out}"); return 1
if os.path.exists(os.path.join(os.path.dirname(FIX_ROOT), "meow_boot.log")):
pass # launcher 日志在 /tmp,忽略
# ---- stdout -> dict ----
presets = {}
famwt = {}
for line in out.splitlines():
line = line.strip()
if line.startswith("fonts="):
continue
if line and line[0].isdigit():
# " 0 Label -> path (fam=.. wt=..)" 但行首 i 数字后无空格分隔可靠断言,用 split
parts = line.split(" -> ")
if len(parts) != 2:
continue
idx = int(parts[0].split()[0])
rest, famwt_part = parts[1].split(" (fam=", 1)
path = rest.strip()
fam = famwt_part.split(" wt=")[0]
wt = famwt_part.split(" wt=")[1].rstrip(")")
presets[path] = idx
famwt[path] = (fam, wt)
elif line.startswith("fonts="):
n = int(line.split("=")[1])
if n != len(presets):
print(f"FAIL: fonts={n} 但解析到 {len(presets)} 条\n{out}"); return 1
fails = 0
def check(cond, msg):
nonlocal fails
if not cond:
fails += 1
print(f"FAIL: {msg}")
else:
print(f"ok : {msg}")
p1 = "MeowOS/fonts/misans/regular/font.png"
p2 = "MeowOS/fonts/misans/bold/font.png"
p3 = "MeowOS/fonts/meslo/regular/font.png"
p0 = "MeowOS/fonts/default/regular/font.png"
# 两级扫描产出(默认 + 3 套,共 4)
check(len(presets) == 4, f"扫描产出 4 套(实际 {len(presets)})")
check(p0 in presets and presets[p0] == 0, "索引 0 = default/regular")
check(p1 in presets, "misans/regular 进列表")
check(p2 in presets, "misans/bold 进列表")
check(p3 in presets, "meslo/regular 进列表")
check(famwt.get(p1) == ("misans", "regular"), "fam/wt(misans/regular) 系家族+字重")
check(famwt.get(p2) == ("misans", "bold"), "fam/wt(misans/bold) 系家族+字重")
# 缺件 / 旧式一级 / 顶层文件 → 不产出
check(all("broken" not in k for k in presets), "缺 font.idx 的 broken/regular 被跳过")
check(all("legacy" not in k for k in presets), "旧式一级布局 legacy(无 weight 级)被忽略(迁移后需重扫)")
check(all("stray" not in k for k in presets), "顶层 stray.png(非目录)被忽略")
# weight 解析 → 显示名 `Name Weight(common≈4k)`
labels = {os.path.basename(os.path.dirname(os.path.dirname(p))):
p for p in presets}
# 用 stdout 里的 Label 校验(weight 大写 + 档位标记)
out_has = "MiSans Regular(common≈4k)" in out
out_has_bold = "MiSans Bold(common≈4k)" in out
check(out_has, "label 含 `MiSans Regular(common≈4k)`(weight 解析+大小标记)")
check(out_has_bold, "label 含 `MiSans Bold(common≈4k)`(bold 字重解析)")
print(f"\n{'RESULT: PASS' if fails == 0 else 'RESULT: FAIL'} ({fails} failures)")
return 1 if fails else 0
if __name__ == "__main__":
sys.exit(main())