184 lines
8.5 KiB
Python
184 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Офлайн-тесты команды apply: YAML-конфиг → argv существующих команд (без железа).
|
|
|
|
Запуск: python3 tests/test_apply.py (или pytest tests/test_apply.py).
|
|
|
|
Эталон — katana.yaml.example: конфиг должен разбираться и превращаться
|
|
в валидные argv для build_parser().parse_args() (валидация значений —
|
|
та же, что у обычных команд).
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from katana import (APPLY_SECTION_ORDER, CONFIG_KEYS, _build_macro_slots,
|
|
build_parser, config_argv, load_config, macro_argv)
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
EXAMPLE = os.path.join(ROOT, "katana.yaml.example")
|
|
|
|
|
|
def parse_ok(argv):
|
|
"""parse_args без запуска команды: проверяет валидность значений."""
|
|
return build_parser().parse_args(argv)
|
|
|
|
|
|
def test_example_config_loads():
|
|
"""Пример конфига читается, секции — только разрешённые."""
|
|
cfg = load_config(EXAMPLE)
|
|
assert set(cfg) <= set(APPLY_SECTION_ORDER), set(cfg)
|
|
assert {"macros", "remap", "paint", "lighting"} <= set(cfg)
|
|
# YAML-ключи слотов макросов парсятся как int
|
|
assert sorted(cfg["macros"]) == [0, 1]
|
|
|
|
|
|
def test_example_config_argv_valid():
|
|
"""Все секции примера превращаются в валидные argv существующих команд.
|
|
|
|
save=False для всех секций, кроме последней — так делает cmd_apply
|
|
(save коммитит всю RAM, поэтому он только у последней команды).
|
|
"""
|
|
cfg = load_config(EXAMPLE)
|
|
for slot, spec in cfg["macros"].items():
|
|
ns = parse_ok(macro_argv(slot, spec, save=False))
|
|
assert ns.cmd == "macro" and ns.macro_cmd == "set" and not ns.save
|
|
ns = parse_ok(config_argv("remap", cfg["remap"], save=False))
|
|
assert ns.cmd == "remap" and "caps=macro0" in ns.key and ns.no_save
|
|
ns = parse_ok(config_argv("paint", cfg["paint"], save=False))
|
|
assert ns.cmd == "paint" and ns.all == (0, 0, 0) and ns.no_save
|
|
assert ns.wasd == (30, 144, 255) # dodgerblue через parse_color
|
|
assert "esc=ff0000" in ns.key
|
|
ns = parse_ok(config_argv("lighting", cfg["lighting"], save=True))
|
|
assert ns.cmd == "mode" and ns.n == 1 # static (последняя секция — с save)
|
|
assert ns.color == (255, 0, 0) and not ns.no_save
|
|
|
|
|
|
def test_remap_argv():
|
|
"""[remap]: keys → --key, save управляется cmd_apply (не ключом секции)."""
|
|
argv = config_argv("remap", {"keys": {"caps": "copy", "menu": "meta+e"}},
|
|
save=False)
|
|
assert argv == ["remap", "--key", "caps=copy", "--key", "menu=meta+e",
|
|
"--no-save"]
|
|
argv = config_argv("remap", {"keys": {"caps": "copy"}}, save=True)
|
|
assert argv == ["remap", "--key", "caps=copy"] # save=True — без --no-save
|
|
argv = config_argv("remap", {"clear": True}, save=False)
|
|
assert "--clear" in argv
|
|
parse_ok(argv)
|
|
|
|
|
|
def test_lighting_argv():
|
|
"""[lighting]: имена/номера режимов, off/default, опциональные поля."""
|
|
ns = parse_ok(config_argv("lighting", {"mode": "static", "speed": 5},
|
|
save=False))
|
|
assert (ns.cmd, ns.n, ns.speed) == ("mode", 1, 5) and ns.no_save
|
|
ns = parse_ok(config_argv("lighting", {"mode": 1, "color": "ff8800"},
|
|
save=True))
|
|
assert (ns.cmd, ns.n, ns.color) == ("mode", 1, (255, 136, 0))
|
|
assert config_argv("lighting", {"mode": "off"}, save=False) == \
|
|
["off", "--no-save"]
|
|
assert config_argv("lighting", {"mode": "off"}, save=True) == ["off"]
|
|
# default сам делает save, флаг не нужен
|
|
assert config_argv("lighting", {"mode": "default"}, save=False) == ["default"]
|
|
|
|
|
|
def test_paint_argv():
|
|
"""[paint]: all/keys/rows/shorthand-алиасы/black/keep/brightness."""
|
|
argv = config_argv("paint", {
|
|
"all": "black", "keys": {"esc": "red"}, "rows": {1: "202020"},
|
|
"wasd": "dodgerblue", "black": True, "keep": True, "brightness": 10,
|
|
}, save=False)
|
|
ns = parse_ok(argv)
|
|
assert ns.all == (0, 0, 0) and ns.black and ns.keep and ns.no_save
|
|
assert ns.brightness == 10
|
|
assert "esc=red" in ns.key
|
|
assert ns.row1 == (32, 32, 32) # --row1 202020
|
|
assert ns.wasd == (30, 144, 255)
|
|
|
|
|
|
def test_macro_argv():
|
|
"""[macros]: tokens через '--', xml, save управляется cmd_apply."""
|
|
argv = macro_argv(0, {"tokens": ["+lctrl", "a@50", "-lctrl"]}, save=False)
|
|
assert argv == ["macro", "set", "0", "--", "+lctrl", "a@50", "-lctrl"]
|
|
ns = parse_ok(argv) # токены с '-' не съедаются как опции
|
|
assert ns.index == 0 and ns.tokens == ["+lctrl", "a@50", "-lctrl"] and not ns.save
|
|
argv = macro_argv(1, {"xml": "test.xml"}, save=True)
|
|
# --save строго ДО '--': после '--' argparse считает всё позиционным
|
|
ns = parse_ok(argv)
|
|
assert ns.xml == "test.xml" and ns.save
|
|
for spec in ({"tokens": ["a"], "xml": "t.xml"}, {}):
|
|
try:
|
|
macro_argv(0, spec, save=False)
|
|
except SystemExit:
|
|
pass
|
|
else:
|
|
raise AssertionError(f"spec {spec} must fail")
|
|
|
|
|
|
def test_build_macro_slots():
|
|
"""[macros] → слоты одним блобом: несколько слотов, пропуски, ошибки."""
|
|
slots = _build_macro_slots({
|
|
0: {"tokens": ["+lctrl", "a@50", "-lctrl"]},
|
|
2: {"tokens": ["b"]},
|
|
})
|
|
# слот 1 — пропуск (пустой), слоты 0 и 2 заполнены
|
|
assert len(slots) == 3 and len(slots[0]) == 4 and slots[1] == [] \
|
|
and len(slots[2]) == 2
|
|
slots = _build_macro_slots({5: {"xml": "raw-data/katana-v1/test.xml"}})
|
|
assert len(slots) == 6 and slots[0] == [] and len(slots[5]) == 8
|
|
for slots_cfg in ({"0": {"tokens": ["a"], "xml": "t.xml"}}, # оба поля
|
|
{"0": {}}, # ни одного
|
|
{"x": {"tokens": ["a"]}}, # не число
|
|
{"0": {"tokens": []}}, # пустой список
|
|
{"0": {"tokens": ["a"], "save": True}}): # save не поддержан
|
|
try:
|
|
_build_macro_slots(slots_cfg)
|
|
except SystemExit:
|
|
pass
|
|
else:
|
|
raise AssertionError(f"{slots_cfg} must fail")
|
|
|
|
|
|
def test_unknown_keys_and_sections():
|
|
"""Опечатки в ключах секций и неизвестные секции отклоняются.
|
|
Ключ 'save' в секциях не поддерживается (apply управляет save сам)."""
|
|
for section, values in (
|
|
("remap", {"key": {"caps": "copy"}}), # опечатка keys
|
|
("paint", {"colour": "red"}), # опечатка color
|
|
("lighting", {"mode": "static", "spead": 5}), # опечатка speed
|
|
("remap", {"keys": {"caps": "copy"}, "save": True}), # save не поддержан
|
|
("paint", {"all": "black", "save": False}), # save не поддержан
|
|
("lighting", {"mode": "off", "save": True}), # save не поддержан
|
|
):
|
|
try:
|
|
config_argv(section, values, save=False)
|
|
except SystemExit as e:
|
|
assert "unknown keys" in str(e), e
|
|
else:
|
|
raise AssertionError(f"{section} {values} must fail")
|
|
try:
|
|
load_config("/nonexistent/katana.yaml")
|
|
except SystemExit as e:
|
|
assert "not found" in str(e), e
|
|
else:
|
|
raise AssertionError("missing config must fail")
|
|
|
|
|
|
def test_section_order():
|
|
"""Порядок применения: macros последними — запись макросов сбивает
|
|
режим подсветки, а save коммитит всю RAM (см. katana.py)."""
|
|
assert APPLY_SECTION_ORDER == ("remap", "paint", "lighting", "raw", "macros")
|
|
assert set(CONFIG_KEYS) == {"remap", "paint", "lighting"}
|
|
|
|
|
|
def main():
|
|
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
|
for test in tests:
|
|
test()
|
|
print(f"ok: {test.__name__}")
|
|
print(f"\n{len(tests)} tests passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|