Initial commit
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user