from __future__ import annotations import argparse import base64 import json from pathlib import Path from typing import Sequence from app.animations.store import AnimationStore PRODUCTION_DATA_ROOT = Path("/var/lib/matrix-screen-controller") REQUIRED_BASENAME_PREFIX = "matrix-screen-controller-preview-perf-" class FixtureError(RuntimeError): pass def validate_empty_test_root(value: str | Path) -> Path: path = Path(value).expanduser().resolve(strict=True) if not path.is_dir(): raise FixtureError("pressure-test data root must be an existing directory") if path == Path(path.anchor) or path == Path.home().resolve(): raise FixtureError("pressure-test data root is too broad") if path == PRODUCTION_DATA_ROOT or PRODUCTION_DATA_ROOT in path.parents: raise FixtureError("refusing to use the production data root") if not path.name.startswith(REQUIRED_BASENAME_PREFIX): raise FixtureError( f"pressure-test directory name must start with {REQUIRED_BASENAME_PREFIX!r}" ) if any(path.iterdir()): raise FixtureError("pressure-test data root must be empty") return path def solid_scene(sequence: int) -> dict[str, object]: if not 0 <= sequence <= 0xFFFFFF: raise FixtureError("fixture sequence exceeds unique RGB color space") color = bytes(((sequence >> 16) & 0xFF, (sequence >> 8) & 0xFF, sequence & 0xFF)) return { "version": 1, "width": 64, "height": 64, "pixelRgb": base64.b64encode(color * (64 * 64)).decode("ascii"), "elements": [], } def fixture_name(index: int) -> str: if index % 3 == 0: return f"压力动图_{index:03d}_" + "很长的中文名称" * 7 if index % 3 == 1: return f"stress_{index:03d}_" + "abcdefghijklmnopqrstuvwxyz" * 2 return f"混合Stress_{index:03d}_" + "无空格LongName" * 5 def generate_fixture( data_root: Path, *, animation_count: int = 100, frames_per_animation: int = 100, duration_ms: int = 50, ) -> dict[str, int | str]: if not 1 <= animation_count <= 1000: raise FixtureError("animation count must be within 1..1000") if not 1 <= frames_per_animation <= 30_000: raise FixtureError("frames per animation must be within 1..30000") if not 50 <= duration_ms <= 604_800_000: raise FixtureError("frame duration must be within 50..604800000ms") if animation_count * frames_per_animation > 0x1000000: raise FixtureError("fixture needs more than 24-bit unique solid colors") store = AnimationStore(data_root) sequence = 0 for animation_index in range(animation_count): def frames(): nonlocal sequence for _frame_index in range(frames_per_animation): scene = solid_scene(sequence) sequence += 1 yield {"scene": scene, "name": None, "duration_ms": duration_ms} store.create_from_frames(fixture_name(animation_index), frames()) return { "data_root": str(data_root), "animations": animation_count, "frames_per_animation": frames_per_animation, "total_frames": animation_count * frames_per_animation, "duration_ms": duration_ms, } def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Generate isolated 64x64 animations for thumbnail performance tests." ) parser.add_argument("--data-root", required=True) parser.add_argument("--animations", type=int, default=100) parser.add_argument("--frames", type=int, default=100) parser.add_argument("--duration-ms", type=int, default=50) return parser def main(argv: Sequence[str] | None = None) -> int: args = build_parser().parse_args(argv) try: root = validate_empty_test_root(args.data_root) result = generate_fixture( root, animation_count=args.animations, frames_per_animation=args.frames, duration_ms=args.duration_ms, ) except (FixtureError, OSError, ValueError) as exc: print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False)) return 1 print(json.dumps({"ok": True, **result}, ensure_ascii=False)) return 0 if __name__ == "__main__": raise SystemExit(main())