- parser: FSM 解析器与仓颉版逐语义对齐,修 S01/S02/S04/Q05 - robot/hardvars: 六类硬件变量工厂,修 r.cj:911 重名 FIXME、Q07 倒序释放 - plugin: Python 插件替代 Lua 桥接,修 S07 三级路径查找 - hd: i2c(每fd锁 Q03/S06)/spi/servo/kalman/mpu6050(S05/Q06)/speaker(S03 实声) - modules: PCA9685 与 Lua 版逐字节对齐;ADS1115 重写(experimental);ADS1256 空桩 - tests: 42 项 pytest 全绿(Mock 总线 journal 断言) - CLI: --dry-run / --test roundtrip / --verbose;运行时零第三方依赖
113 lines
3.4 KiB
Python
113 lines
3.4 KiB
Python
"""解析器测试:FSM / 列数校验(S01) / 占位符语义 / 序列化往返 / 全量脚本"""
|
||
|
||
import io
|
||
|
||
import pytest
|
||
|
||
from pybot.parser import (DoesNotSync, MatrixParser, MatrixSaver,
|
||
get_mt, parse_mt)
|
||
from tests.conftest import SCRIPTS_DIR
|
||
|
||
|
||
def parse_all(text):
|
||
parser = MatrixParser(text)
|
||
objects = []
|
||
while True:
|
||
obj = parser.parse()
|
||
if obj is None:
|
||
break
|
||
objects.append(obj)
|
||
return objects
|
||
|
||
|
||
# ---------------- 基本解析 ----------------
|
||
|
||
def test_parse_simple_block():
|
||
objs = parse_all("@Foo\nname val;\na 1;\nb 2;\n@\n")
|
||
assert len(objs) == 1
|
||
m = objs[0]
|
||
assert m.name == "Foo"
|
||
assert m.size == 3 and m.col == 2
|
||
assert m[0, 0] == "name" and m[2, 1] == "2"
|
||
|
||
|
||
def test_parse_multiple_blocks_and_comments():
|
||
text = ("# 头部注释\n"
|
||
"@A\nx y;\n1 2;\n@\n"
|
||
"@B\nn;\nv; # 行内注释\n@\n")
|
||
objs = parse_all(text)
|
||
assert [o.name for o in objs] == ["A", "B"]
|
||
assert objs[1][1, 0] == "v"
|
||
|
||
|
||
def test_parse_std_mt():
|
||
objs = parse_all((SCRIPTS_DIR / "std.mt").read_text(encoding="utf-8"))
|
||
names = [o.name for o in objs]
|
||
assert names == ["Alias", "I2C", "PCA9685", "MPU6050_BIND",
|
||
"Speaker", "Servo", "Action"]
|
||
servo = objs[names.index("Servo")]
|
||
assert servo.size == 11 and servo.col == 6 # 表头 + 10 舵机
|
||
# 注释掉的 MPU6050_BIND 数据行不产生内容
|
||
assert objs[names.index("MPU6050_BIND")].size == 1
|
||
|
||
|
||
@pytest.mark.parametrize("script", sorted(SCRIPTS_DIR.glob("*.mt")),
|
||
ids=lambda p: p.name)
|
||
def test_parse_all_scripts(script):
|
||
"""全量 scripts/*.mt 解析:块内每行列数一致且非空"""
|
||
objs = parse_all(script.read_text(encoding="utf-8"))
|
||
assert objs, f"{script.name}: no blocks parsed"
|
||
for o in objs:
|
||
assert o.name
|
||
for row in o.values:
|
||
assert len(row) == o.col
|
||
|
||
|
||
# ---------------- S01:列数校验 ----------------
|
||
|
||
def test_does_not_sync():
|
||
with pytest.raises(DoesNotSync) as ei:
|
||
parse_all("@Foo\na b c;\n1 2;\n@\n")
|
||
assert ei.value.expected == 3 and ei.value.actual == 2
|
||
|
||
|
||
def test_missing_semicolon_merges_lines():
|
||
"""漏写分号导致跨行合并成一行(S04 回归场景),列数不齐即报错"""
|
||
with pytest.raises(DoesNotSync):
|
||
parse_all("@Foo\na b;\n1 2\n3 4;\n@\n")
|
||
|
||
|
||
# ---------------- 占位符语义 ----------------
|
||
|
||
def _matrix(text):
|
||
return parse_all(text)[0]
|
||
|
||
|
||
def test_placeholder_recursion():
|
||
m = _matrix("@T\nname v;\na 5;\nb -;\nc -;\n@\n")
|
||
assert get_mt(m, 3, 1) == "5" # 递归两级到首行
|
||
assert parse_mt(m, 3, 1) == 5.0
|
||
|
||
|
||
def test_placeholder_first_row():
|
||
"""首行 '-':get_mt 返回字面 '-';parse_mt 解析为 typ("0")
|
||
⚠️ 与 actpred/mt_parser.py 不同(后者用 @Servo 初始角度填充),不得误统一"""
|
||
m = _matrix("@T\nname v;\na -;\n@\n")
|
||
assert get_mt(m, 1, 1) == "-"
|
||
assert parse_mt(m, 1, 1) == 0.0
|
||
assert parse_mt(m, 1, 1, int) == 0
|
||
|
||
|
||
# ---------------- 序列化往返 ----------------
|
||
|
||
def test_roundtrip():
|
||
text = (SCRIPTS_DIR / "std.mt").read_text(encoding="utf-8")
|
||
objs = parse_all(text)
|
||
out = io.StringIO()
|
||
MatrixSaver(out).save(objs)
|
||
objs2 = parse_all(out.getvalue())
|
||
assert len(objs2) == len(objs)
|
||
for a, b in zip(objs, objs2):
|
||
assert a.name == b.name
|
||
assert a.values == b.values
|