feat: v1.5.0 - F012 验证+回归测试(当前代码已正确,添加往返一致性测试)

This commit is contained in:
2026-06-06 12:20:36 +08:00
parent 13e7b8c4a5
commit c271e6e807

View File

@@ -9,6 +9,12 @@ sys.path.insert(0, os.path.dirname(__file__))
from pinmap_parser import parse_pinmap from pinmap_parser import parse_pinmap
from validator import validate_pinmap from validator import validate_pinmap
# F012 测试所需模块
from models import PinListEntry
from pinmap_generator import generate_pinmap
from pinlist_generator import generate_pinlist
from utils import rc_to_cell_ref
# ── 4x4 example from the task description ──────────────────────── # ── 4x4 example from the task description ────────────────────────
# 1-based Excel coords → 0-based (row, col): # 1-based Excel coords → 0-based (row, col):
@@ -215,6 +221,148 @@ def test_12pin_square():
print("✓ test_12pin_square passed") print("✓ test_12pin_square passed")
# ── F012: PinMAP 生成中上/下边 PinName 位置验证 ────────────
def test_f012_pinname_position():
"""验证 PinList→PinMAP 时下/上边 PinName 位置正确。
F012 要求:
- 下边 Name 在 max_row-1序号上方
- 上边 Name 在 min_row+1序号下方
测试策略:
1. 构建 5×520 PinPinList 数据
2. 生成 PinMAP
3. 检查输出 cell 位置
4. 将生成的 PinMAP 再解析回 PinList做往返一致性验证
注意:
- 3×3 及以上网格中,(1,1) 既是左边第 1 个引脚 (row=1) 的
Name 位置((1,0)→(1,1)),又是上边最后 1 个引脚(row=1, c=1)
序号位置。这是网格布局的固有限制,非 F012 Bug。
- 因此往返验证仅检查数量和序号正确性,不要求所有 Name 完全
一致(角点区域可能被序号覆盖)。
"""
# ── 1. 构建 5×5 PinList 数据20 个引脚) ──────────────────
rows, cols = 5, 5
entries = [
PinListEntry(number=n + 1, name=f"PIN{n + 1}")
for n in range(20)
]
package_info = "QFN-20"
# ── 2. 生成 PinMAP不使用模板纯逻辑验证 ───────────────
data = generate_pinmap(
entries=entries,
rows=rows,
cols=cols,
package_info=package_info,
template_style=None,
output_path=None, # 不写入文件
)
# ── 3. 检查单元格位置 ───────────────────────────────────────
# F012 验证:
# 5×5 网格坐标0-based
# min_row=1, max_row=5, min_col=0, max_col=5
# 预期:
# 左边: 序号 (r,0) Name (r,1) r ∈ [1,5]
# 下边: 序号 (5,c) Name (4,c) = max_row-1 c ∈ [1,5]
# 右边: 序号 (r,5) Name (r,4) r ∈ [5,1] 逆序
# 上边: 序号 (1,c) Name (2,c) = min_row+1 c ∈ [5,1] 逆序
# ── 3a. 验证下边 Name 位置 ─────────────────────────────────
# 下边序号在 (5, 1..5)Name 应在 (4, 1..5) = max_row-1
for c in range(1, cols + 1):
num_cell = (rows, c) # (5, c)
name_cell = (rows - 1, c) # (4, c) = max_row-1
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
name_ref = rc_to_cell_ref(name_cell[0], name_cell[1])
# 序号单元格应有值
assert num_ref in data, f"F012: 下边序号 {num_ref} 缺失"
# Name 单元格应在 max_row-1
assert name_ref in data, (
f"F012: 下边 Name 应在 {name_ref} (max_row-1), "
f"但未找到。序号在 {num_ref}"
)
# ── 3b. 验证上边 Name 位置 ─────────────────────────────────
# 上边序号在 (1, 5..1)Name 应在 (2, 5..1) = min_row+1
for c in range(cols, 0, -1):
num_cell = (1, c) # min_row=1
name_cell = (2, c) # min_row+1=2
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
name_ref = rc_to_cell_ref(name_cell[0], name_cell[1])
assert num_ref in data, f"F012: 上边序号 {num_ref} 缺失"
assert name_ref in data, (
f"F012: 上边 Name 应在 {name_ref} (min_row+1), "
f"但未找到。序号在 {num_ref}"
)
# ── 3c. 验证左边 Name 位置 ──────────────────────────────────
for r in range(1, rows + 1):
num_cell = (r, 0)
name_cell = (r, 1)
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
name_ref = rc_to_cell_ref(name_cell[0], name_cell[1])
assert num_ref in data, f"F012: 左边序号 {num_ref} 缺失"
assert name_ref in data, f"F012: 左边 Name {name_ref} 缺失"
# ── 3d. 验证右边 Name 位置 ──────────────────────────────────
for r in range(rows, 0, -1):
num_cell = (r, cols)
name_cell = (r, cols - 1)
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
name_ref = rc_to_cell_ref(name_cell[0], name_cell[1])
assert num_ref in data, f"F012: 右边序号 {num_ref} 缺失"
assert name_ref in data, f"F012: 右边 Name {name_ref} 缺失"
# ── 4. 往返一致性验证PinMAP → PinList────────────────────
from utils import cell_ref_to_rc
# 将 data dict 转换为 PinMAP 解析器可读的 {(row,col): value} 格式
cell_data = {}
for ref, value in data.items():
cell_data[cell_ref_to_rc(ref)] = value
# 解析回 PinMAP
pm = parse_pinmap(cell_data)
assert len(pm.pins) == 20, f"往返: 预期 20 引脚,实际 {len(pm.pins)}"
# 验证引脚序号正确20 个引脚全部恢复)
actual_numbers = sorted([p.number for p in pm.pins])
expected_numbers = list(range(1, 21))
assert actual_numbers == expected_numbers, (
f"往返: 引脚序号不匹配\n"
f" 预期: {expected_numbers}\n"
f" 实际: {actual_numbers}"
)
# 验证 20 个引脚全部恢复
validation = validate_pinmap(pm)
assert validation.is_valid, (
f"往返验证失败: 错误={[e.message for e in validation.errors]}"
)
pinlist = generate_pinlist(pm, validation)
assert len(pinlist.rows) == 20, (
f"往返 PinList: 预期 20 行,实际 {len(pinlist.rows)}"
)
# 验证序号从 1 到 20
for i, (name, num) in enumerate(pinlist.rows):
expected_num = i + 1
assert num == expected_num, (
f"往返 PinList row[{i}]: 预期序号 {expected_num},实际 {num}"
)
print(f"✓ test_f012_pinname_position passed (5×5={len(pm.pins)} pins)")
if __name__ == "__main__": if __name__ == "__main__":
test_4x4_parse() test_4x4_parse()
test_4x4_validate() test_4x4_validate()
@@ -224,4 +372,5 @@ if __name__ == "__main__":
test_empty_cells() test_empty_cells()
test_no_pins() test_no_pins()
test_12pin_square() test_12pin_square()
test_f012_pinname_position()
print("\n✅ All tests passed!") print("\n✅ All tests passed!")