v1.3.0: 修复pinmap_layout周长公式,新增PinList→PinMAP反向转换完整支持
This commit is contained in:
@@ -175,16 +175,13 @@ def test_list_to_map(r: TestRunner):
|
||||
tmpdir = tempfile.mkdtemp(prefix="pinmap_test_")
|
||||
|
||||
try:
|
||||
# ── TC-LM-001: 4×4 PinList → 2×2 PinMAP (16引脚) ──
|
||||
# 2×rows + 2×cols - 4 = 2*4 + 2*4 - 4 = 12, not 16
|
||||
# Actually: for a 4×4 grid: 2*4 + 2*4 - 4 = 12 pins
|
||||
# For 16 pins on a square: 2r + 2c - 4 = 16 → r=c=5 → 5×5 grid
|
||||
# Let's use 5×5 grid for 16 pins
|
||||
# ── TC-LM-001: 5×5 PinList → PinMAP (20引脚) ──
|
||||
# v1.3: (r+c)*2 = (5+5)*2 = 20 pins
|
||||
def _tc_lm_001(result):
|
||||
# 5×5 grid → 2*5 + 2*5 - 4 = 16 pins
|
||||
data = {'A1': 'QFP-16'}
|
||||
for i in range(1, 17):
|
||||
row = i + 1 # row 2..17
|
||||
# 5×5 grid → (5+5)*2 = 20 pins
|
||||
data = {'A1': 'QFP-20'}
|
||||
for i in range(1, 21):
|
||||
row = i + 1 # row 2..21
|
||||
data[f'A{row}'] = f'Pin{i}'
|
||||
data[f'B{row}'] = str(i)
|
||||
filepath = os.path.join(tmpdir, 'test_5x5_pinlist.xlsx')
|
||||
@@ -192,8 +189,8 @@ def test_list_to_map(r: TestRunner):
|
||||
|
||||
# Parse
|
||||
pkg, entries = parse_pinlist(filepath)
|
||||
assert pkg == 'QFP-16', f"封装信息应为QFP-16, 实际: {pkg}"
|
||||
assert len(entries) == 16, f"应有16个引脚, 实际: {len(entries)}"
|
||||
assert pkg == 'QFP-20', f"封装信息应为QFP-20, 实际: {pkg}"
|
||||
assert len(entries) == 20, f"应有20个引脚, 实际: {len(entries)}"
|
||||
|
||||
# Validate with 5×5 grid
|
||||
validation = validate_pinlist(entries, 5, 5)
|
||||
@@ -203,35 +200,32 @@ def test_list_to_map(r: TestRunner):
|
||||
# Generate PinMAP
|
||||
output = generate_pinmap(entries, 5, 5, pkg, output_path=None)
|
||||
assert 'A1' in output, "A1应有封装信息"
|
||||
assert output['A1'] == 'QFP-16'
|
||||
assert output['A1'] == 'QFP-20'
|
||||
|
||||
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 5×5布局验证通过")
|
||||
|
||||
r.run("TC-LM-001: 5×5 PinList→PinMAP (16引脚)", _tc_lm_001)
|
||||
r.run("TC-LM-001: 5×5 PinList→PinMAP (20引脚)", _tc_lm_001)
|
||||
|
||||
# ── TC-LM-002: 长方形 PinList → 4×8 PinMAP (32引脚) ──
|
||||
# 2*4 + 2*8 - 4 = 8 + 16 - 4 = 20, not 32
|
||||
# For 32 pins: 2r + 2c - 4 = 32
|
||||
# Try 4×16: 2*4 + 2*16 - 4 = 8 + 32 - 4 = 36, no
|
||||
# Try 6×12: 2*6 + 2*12 - 4 = 12 + 24 - 4 = 32 ✓
|
||||
# ── TC-LM-002: 长方形 PinList → 6×10 PinMAP (32引脚) ──
|
||||
# v1.3: (r+c)*2 = (6+10)*2 = 32 pins
|
||||
def _tc_lm_002(result):
|
||||
data = {'A1': 'LQFP-32'}
|
||||
for i in range(1, 33):
|
||||
row = i + 1
|
||||
data[f'A{row}'] = f'PIN_{i:02d}'
|
||||
data[f'B{row}'] = str(i)
|
||||
filepath = os.path.join(tmpdir, 'test_6x12_pinlist.xlsx')
|
||||
filepath = os.path.join(tmpdir, 'test_6x10_pinlist.xlsx')
|
||||
create_pinlist_xlsx(data, filepath)
|
||||
|
||||
pkg, entries = parse_pinlist(filepath)
|
||||
assert len(entries) == 32, f"应有32个引脚, 实际: {len(entries)}"
|
||||
|
||||
validation = validate_pinlist(entries, 6, 12)
|
||||
validation = validate_pinlist(entries, 6, 10)
|
||||
assert validation.is_valid, f"验证应通过: {validation.errors}"
|
||||
|
||||
# Generate and write to file
|
||||
outpath = os.path.join(tmpdir, 'test_6x12_pinmap.xlsx')
|
||||
output = generate_pinmap(entries, 6, 12, pkg, output_path=outpath)
|
||||
outpath = os.path.join(tmpdir, 'test_6x10_pinmap.xlsx')
|
||||
output = generate_pinmap(entries, 6, 10, pkg, output_path=outpath)
|
||||
assert os.path.exists(outpath), "输出文件应存在"
|
||||
|
||||
# Verify output can be read back
|
||||
@@ -239,16 +233,16 @@ def test_list_to_map(r: TestRunner):
|
||||
assert (0, 0) in out_cells, "A1应有数据"
|
||||
assert out_cells[(0, 0)] == 'LQFP-32', f"A1应为LQFP-32, 实际: {out_cells.get((0,0))}"
|
||||
|
||||
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 6×12布局+文件输出验证通过")
|
||||
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 6×10布局+文件输出验证通过")
|
||||
|
||||
r.run("TC-LM-002: 6×12 PinList→PinMAP (32引脚)", _tc_lm_002)
|
||||
r.run("TC-LM-002: 6×10 PinList→PinMAP (32引脚)", _tc_lm_002)
|
||||
|
||||
# ── TC-LM-003: 带模板文件的转换 ──
|
||||
# 先用一个正常PinMAP作为模板
|
||||
# v1.3: 5×5 grid → (5+5)*2 = 20 pins
|
||||
def _tc_lm_003(result):
|
||||
# 创建模板 PinMAP
|
||||
template_data = {'A1': 'QFP-16'}
|
||||
for i in range(1, 17):
|
||||
template_data = {'A1': 'QFP-20'}
|
||||
for i in range(1, 21):
|
||||
row = i + 1
|
||||
template_data[f'A{row}'] = f'Pin{i}'
|
||||
template_data[f'B{row}'] = str(i)
|
||||
@@ -258,8 +252,8 @@ def test_list_to_map(r: TestRunner):
|
||||
write_xlsx_with_style(template_data, template_path)
|
||||
|
||||
# 创建 PinList 并写入模板同目录
|
||||
data = {'A1': 'QFP-16'}
|
||||
for i in range(1, 17):
|
||||
data = {'A1': 'QFP-20'}
|
||||
for i in range(1, 21):
|
||||
row = i + 1
|
||||
data[f'A{row}'] = f'Pin{i}'
|
||||
data[f'B{row}'] = str(i)
|
||||
@@ -329,10 +323,9 @@ def test_list_to_map(r: TestRunner):
|
||||
r.run("TC-LM-005: Pin序号重复", _tc_lm_005)
|
||||
|
||||
# ── TC-LM-006: Pin 总数不匹配 ──
|
||||
# v1.3: 3×4 grid → (3+4)*2 = 14 pins
|
||||
def _tc_lm_006(result):
|
||||
# 创建8个引脚的PinList,但指定3×3网格(需要8个引脚)
|
||||
# 2*3 + 2*3 - 4 = 8 → 匹配!
|
||||
# 改用3×4网格(需要2*3+2*4-4=10个引脚)
|
||||
# 创建8个引脚的PinList,但3×4网格需要14个引脚
|
||||
data = {'A1': 'QFP-test'}
|
||||
for i in range(1, 9): # 8 pins
|
||||
row = i + 1
|
||||
@@ -342,7 +335,7 @@ def test_list_to_map(r: TestRunner):
|
||||
create_pinlist_xlsx(data, filepath)
|
||||
|
||||
pkg, entries = parse_pinlist(filepath)
|
||||
# 3×4 needs 10 pins, but we have 8
|
||||
# 3×4 needs 14 pins, but we have 8
|
||||
validation = validate_pinlist(entries, 3, 4)
|
||||
assert not validation.is_valid, "应验证失败"
|
||||
assert any("不匹配" in e.message for e in validation.errors), \
|
||||
@@ -352,11 +345,12 @@ def test_list_to_map(r: TestRunner):
|
||||
r.run("TC-LM-006: Pin总数不匹配", _tc_lm_006)
|
||||
|
||||
# ── TC-LM-007: 缺少 PinName ──
|
||||
# v1.3: 2×3 grid → (2+3)*2 = 10 pins
|
||||
def _tc_lm_007(result):
|
||||
data = {'A1': 'QFP-test'}
|
||||
# 6个引脚,其中第2个缺PinName
|
||||
# 2×4 grid: 2*2+2*4-4=6 pins ✓
|
||||
entries_data = [('Pin1', '1'), ('', '2'), ('Pin3', '3'), ('Pin4', '4'), ('Pin5', '5'), ('Pin6', '6')]
|
||||
# 10个引脚,其中第2个缺PinName
|
||||
entries_data = [('Pin1', '1'), ('', '2'), ('Pin3', '3'), ('Pin4', '4'), ('Pin5', '5'),
|
||||
('Pin6', '6'), ('Pin7', '7'), ('Pin8', '8'), ('Pin9', '9'), ('Pin10', '10')]
|
||||
for i, (name, num) in enumerate(entries_data):
|
||||
row = i + 2
|
||||
data[f'A{row}'] = name
|
||||
@@ -376,14 +370,12 @@ def test_list_to_map(r: TestRunner):
|
||||
r.run("TC-LM-007: 缺少PinName (warning)", _tc_lm_007)
|
||||
|
||||
# ── TC-LM-008: 非4倍数提示 ──
|
||||
# v1.3: (r+c)*2 is always even, but may not be multiple of 4
|
||||
# (3+4)*2 = 14, 14 % 4 = 2 → not a multiple of 4
|
||||
def _tc_lm_008(result):
|
||||
# 6个引脚 → 不是4的倍数
|
||||
# 2r+2c-4=6 → try 3×4: 2*3+2*4-4=10, no
|
||||
# try 2×5: 2*2+2*5-4=8, no
|
||||
# try 4×3: 2*4+2*3-4=10, no
|
||||
# Actually: 2r+2c-4=6 → r+c=5 → try r=2,c=3: 4+6-4=6 ✓
|
||||
# 14个引脚 → 不是4的倍数
|
||||
data = {'A1': 'QFP-test'}
|
||||
for i in range(1, 7):
|
||||
for i in range(1, 15):
|
||||
row = i + 1
|
||||
data[f'A{row}'] = f'Pin{i}'
|
||||
data[f'B{row}'] = str(i)
|
||||
@@ -391,22 +383,17 @@ def test_list_to_map(r: TestRunner):
|
||||
create_pinlist_xlsx(data, filepath)
|
||||
|
||||
pkg, entries = parse_pinlist(filepath)
|
||||
validation = validate_pinlist(entries, 2, 3)
|
||||
validation = validate_pinlist(entries, 3, 4)
|
||||
assert validation.is_valid, f"应验证通过: {validation.errors}"
|
||||
# 6 % 4 != 0, 应有 info 提示
|
||||
# 注意: validate_pinlist 返回的 ValidationResult 没有 infos 字段
|
||||
# 但 info 消息是附加到 warnings 中的
|
||||
# 实际上看代码,infos 是单独列表,但 ValidationResult 只有 errors 和 warnings
|
||||
# 所以 info 不会出现在 validation.warnings 中
|
||||
# 让我们直接检查 validate_pinlist 的返回
|
||||
# 14 % 4 != 0, 应有 info 提示
|
||||
result.ok(f"验证通过, Pin数={len(entries)} (非4倍数)")
|
||||
|
||||
r.run("TC-LM-008: 非4倍数提示", _tc_lm_008)
|
||||
|
||||
# ── TC-LM-009: 布局计算正确性 ──
|
||||
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||
def _tc_lm_009(result):
|
||||
# 3×3 grid → 2*3 + 2*3 - 4 = 8 pins
|
||||
entries = [PinListEntry(number=i, name=f'P{i}') for i in range(1, 9)]
|
||||
entries = [PinListEntry(number=i, name=f'P{i}') for i in range(1, 13)]
|
||||
layout = calculate_layout(entries, 3, 3)
|
||||
|
||||
# 验证四条边都有引脚
|
||||
@@ -415,26 +402,25 @@ def test_list_to_map(r: TestRunner):
|
||||
assert 'right' in layout, "应有right边"
|
||||
assert 'top' in layout, "应有top边"
|
||||
|
||||
# 验证引脚数量分配
|
||||
# left: rows=3, bottom: cols-1=2, right: rows-2=1, top: cols-1=2
|
||||
# 验证引脚数量分配 (v1.3: 每条边独立)
|
||||
# left: rows=3, bottom: cols=3, right: rows=3, top: cols=3
|
||||
assert len(layout['left'].pins) == 3, f"left应有3个引脚, 实际: {len(layout['left'].pins)}"
|
||||
assert len(layout['bottom'].pins) == 2, f"bottom应有2个引脚, 实际: {len(layout['bottom'].pins)}"
|
||||
assert len(layout['right'].pins) == 1, f"right应有1个引脚, 实际: {len(layout['right'].pins)}"
|
||||
assert len(layout['top'].pins) == 2, f"top应有2个引脚, 实际: {len(layout['top'].pins)}"
|
||||
assert len(layout['bottom'].pins) == 3, f"bottom应有3个引脚, 实际: {len(layout['bottom'].pins)}"
|
||||
assert len(layout['right'].pins) == 3, f"right应有3个引脚, 实际: {len(layout['right'].pins)}"
|
||||
assert len(layout['top'].pins) == 3, f"top应有3个引脚, 实际: {len(layout['top'].pins)}"
|
||||
|
||||
# 验证总引脚数
|
||||
total = sum(len(e.pins) for e in layout.values())
|
||||
assert total == 8, f"总引脚数应为8, 实际: {total}"
|
||||
assert total == 12, f"总引脚数应为12, 实际: {total}"
|
||||
|
||||
# 验证逆时针顺序: left(1,2,3) → bottom(4,5) → right(6) → top(7,8)
|
||||
# 验证逆时针顺序: left(1,2,3) → bottom(4,5,6) → right(7,8,9) → top(10,11,12)
|
||||
assert layout['left'].pins[0][0] == 1, "left第一个应为Pin1"
|
||||
assert layout['left'].pins[-1][0] == 3, "left最后一个应为Pin3"
|
||||
assert layout['bottom'].pins[0][0] == 4, "bottom第一个应为Pin4"
|
||||
assert layout['right'].pins[0][0] == 6, "right应为Pin6"
|
||||
assert layout['top'].pins[0][0] == 7, "top第一个应为Pin7"
|
||||
assert layout['top'].pins[1][0] == 8, "top第二个应为Pin8"
|
||||
assert layout['right'].pins[0][0] == 7, "right应为Pin7"
|
||||
assert layout['top'].pins[0][0] == 10, "top第一个应为Pin10"
|
||||
|
||||
result.ok(f"布局计算正确: left=3, bottom=2, right=1, top=2, 逆时针顺序正确")
|
||||
result.ok(f"布局计算正确: left=3, bottom=3, right=3, top=3, 逆时针顺序正确")
|
||||
|
||||
r.run("TC-LM-009: 布局计算正确性", _tc_lm_009)
|
||||
|
||||
@@ -470,10 +456,10 @@ def test_list_to_map(r: TestRunner):
|
||||
r.run("TC-LM-011b: 无效尺寸输入(列数<2)", _tc_lm_011b)
|
||||
|
||||
# ── TC-LM-012: 输出文件正确性 ──
|
||||
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||
def _tc_lm_012(result):
|
||||
# 创建4×4 PinList (8 pins, 3×3 grid)
|
||||
data = {'A1': 'QFP-8'}
|
||||
for i in range(1, 9):
|
||||
data = {'A1': 'QFP-12'}
|
||||
for i in range(1, 13):
|
||||
row = i + 1
|
||||
data[f'A{row}'] = f'Pin{i}'
|
||||
data[f'B{row}'] = str(i)
|
||||
@@ -486,26 +472,26 @@ def test_list_to_map(r: TestRunner):
|
||||
|
||||
# 读取输出并验证
|
||||
out_cells = read_xlsx_cells(outpath)
|
||||
assert out_cells[(0, 0)] == 'QFP-8', f"A1应为QFP-8, 实际: {out_cells.get((0,0))}"
|
||||
assert out_cells[(0, 0)] == 'QFP-12', f"A1应为QFP-12, 实际: {out_cells.get((0,0))}"
|
||||
|
||||
# 验证所有8个引脚序号都在输出中
|
||||
# 验证所有12个引脚序号都在输出中
|
||||
found_nums = set()
|
||||
for (row, col), val in out_cells.items():
|
||||
if val.isdigit() and int(val) >= 1:
|
||||
found_nums.add(int(val))
|
||||
assert found_nums == {1, 2, 3, 4, 5, 6, 7, 8}, \
|
||||
f"应包含1-8所有序号, 实际: {sorted(found_nums)}"
|
||||
for part in str(val).split("/"):
|
||||
if part.isdigit() and int(part) >= 1:
|
||||
found_nums.add(int(part))
|
||||
assert found_nums == {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, \
|
||||
f"应包含1-12所有序号, 实际: {sorted(found_nums)}"
|
||||
|
||||
result.ok(f"输出文件验证通过: A1={out_cells[(0,0)]}, 包含Pin1-Pin8")
|
||||
result.ok(f"输出文件验证通过: A1={out_cells[(0,0)]}, 包含Pin1-Pin12")
|
||||
|
||||
r.run("TC-LM-012: 输出文件正确性", _tc_lm_012)
|
||||
|
||||
# ── TC-LM-013: 端到端 roundtrip (PinMAP → PinList → PinMAP) ──
|
||||
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||
def _tc_lm_013(result):
|
||||
# 创建一个符合周长公式的 PinList → PinMAP → PinList roundtrip
|
||||
# 3×3 grid → 2*3+2*3-4=8 pins
|
||||
data = {'A1': 'QFP-8'}
|
||||
for i in range(1, 9):
|
||||
data = {'A1': 'QFP-12'}
|
||||
for i in range(1, 13):
|
||||
row = i + 1
|
||||
data[f'A{row}'] = f'Pin{i}'
|
||||
data[f'B{row}'] = str(i)
|
||||
@@ -523,15 +509,15 @@ def test_list_to_map(r: TestRunner):
|
||||
rt_validation = validate_pinmap(rt_pinmap)
|
||||
rt_pinlist = generate_pinlist(rt_pinmap, rt_validation)
|
||||
|
||||
assert len(rt_pinlist.rows) == 8, \
|
||||
f"Roundtrip后应有8个引脚, 实际: {len(rt_pinlist.rows)}"
|
||||
assert len(rt_pinlist.rows) == 12, \
|
||||
f"Roundtrip后应有12个引脚, 实际: {len(rt_pinlist.rows)}"
|
||||
|
||||
# 验证序号一致
|
||||
orig_nums = [e.number for e in entries]
|
||||
rt_nums = [num for _, num in rt_pinlist.rows]
|
||||
assert orig_nums == rt_nums, f"序号应一致: {orig_nums} vs {rt_nums}"
|
||||
|
||||
result.ok(f"Roundtrip成功: PinList(8) → PinMAP(3×3) → PinList({len(rt_pinlist.rows)}), 序号一致")
|
||||
result.ok(f"Roundtrip成功: PinList(12) → PinMAP(3×3) → PinList({len(rt_pinlist.rows)}), 序号一致")
|
||||
|
||||
r.run("TC-LM-013: 端到端Roundtrip (MAP→List→MAP)", _tc_lm_013)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user