125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
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())
|