feat: 完善 MCU 芯片自动化测试架构
- 重构为三角色协作:人+Arch AI+执行AI - 新增 Excel 寄存器表格解析工具,自动生成测试代码 - 新增串口日志分析工具,自动生成测试报告 - 完善项目文档:AGENTS.md、README.md - 创建自动化测试架构设计文档 - 添加示例测试任务 P01-001
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
串口日志分析器:分析测试输出,验证结果
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class LogAnalyzer:
|
||||
def __init__(self, log_file):
|
||||
self.log_file = log_file
|
||||
self.results = {
|
||||
'total': 0,
|
||||
'passed': 0,
|
||||
'failed': 0,
|
||||
'tests': []
|
||||
}
|
||||
|
||||
def analyze(self):
|
||||
"""分析日志文件"""
|
||||
with open(self.log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_test = None
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
# 匹配 [TEST] 开始
|
||||
test_start = re.match(r'\[TEST\]\s*(.*?):\s*开始', line)
|
||||
if test_start:
|
||||
current_test = {
|
||||
'name': test_start.group(1),
|
||||
'status': 'running',
|
||||
'reg_tests': [],
|
||||
'fails': []
|
||||
}
|
||||
self.results['total'] += 1
|
||||
continue
|
||||
|
||||
# 匹配 [PASS]
|
||||
pass_match = re.match(r'\[PASS\]\s*(.*?):\s*(.*)', line)
|
||||
if pass_match and current_test:
|
||||
reg_name = pass_match.group(1)
|
||||
detail = pass_match.group(2)
|
||||
current_test['reg_tests'].append({
|
||||
'name': reg_name,
|
||||
'status': 'PASS',
|
||||
'detail': detail
|
||||
})
|
||||
continue
|
||||
|
||||
# 匹配 [FAIL]
|
||||
fail_match = re.match(r'\[FAIL\]\s*(.*?):\s*(.*)', line)
|
||||
if fail_match and current_test:
|
||||
reg_name = fail_match.group(1)
|
||||
detail = fail_match.group(2)
|
||||
current_test['reg_tests'].append({
|
||||
'name': reg_name,
|
||||
'status': 'FAIL',
|
||||
'detail': detail
|
||||
})
|
||||
current_test['fails'].append(f"{reg_name}: {detail}")
|
||||
continue
|
||||
|
||||
# 匹配 [TEST] 结束
|
||||
test_end = re.match(r'\[TEST\]\s*(.*?):\s*结束', line)
|
||||
if test_end and current_test:
|
||||
if current_test['fails']:
|
||||
current_test['status'] = 'FAIL'
|
||||
self.results['failed'] += 1
|
||||
else:
|
||||
current_test['status'] = 'PASS'
|
||||
self.results['passed'] += 1
|
||||
self.results['tests'].append(current_test)
|
||||
current_test = None
|
||||
|
||||
return self.results
|
||||
|
||||
def generate_report(self, output_file):
|
||||
"""生成测试报告"""
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write("=" * 60 + "\n")
|
||||
f.write("自动化测试报告\n")
|
||||
f.write("=" * 60 + "\n\n")
|
||||
f.write(f"总测试数: {self.results['total']}\n")
|
||||
f.write(f"通过: {self.results['passed']}\n")
|
||||
f.write(f"失败: {self.results['failed']}\n\n")
|
||||
f.write("-" * 60 + "\n")
|
||||
f.write("详细结果:\n\n")
|
||||
|
||||
for test in self.results['tests']:
|
||||
f.write(f"测试: {test['name']}\n")
|
||||
f.write(f"状态: {'✅ PASS' if test['status'] == 'PASS' else '❌ FAIL'}\n")
|
||||
if test['fails']:
|
||||
f.write("失败项:\n")
|
||||
for fail in test['fails']:
|
||||
f.write(f" - {fail}\n")
|
||||
f.write("\n")
|
||||
|
||||
print(f"报告已生成: {output_file}")
|
||||
print(f"统计: {self.results['passed']}/{self.results['total']} 通过")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='串口日志分析器')
|
||||
parser.add_argument('log_file', help='日志文件路径')
|
||||
parser.add_argument('-o', '--output', help='输出报告文件', default='test_report.txt')
|
||||
args = parser.parse_args()
|
||||
|
||||
analyzer = LogAnalyzer(args.log_file)
|
||||
analyzer.analyze()
|
||||
analyzer.generate_report(args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Excel 寄存器表格解析工具
|
||||
- 生成寄存器定义头文件
|
||||
- 生成自动化测试代码
|
||||
- 生成测试描述文件
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def parse_register_excel(file_path):
|
||||
"""解析 Excel 寄存器表格"""
|
||||
df = pd.read_excel(file_path)
|
||||
print(f"=== 解析: {file_path} ===")
|
||||
print(df.head())
|
||||
print(f"\n总记录数: {len(df)}")
|
||||
return df
|
||||
|
||||
|
||||
def generate_header_file(df, output_path):
|
||||
"""生成 C 头文件"""
|
||||
with open(output_path, 'w') as f:
|
||||
f.write("#ifndef REGISTERS_H\n")
|
||||
f.write("#define REGISTERS_H\n\n")
|
||||
f.write("// 自动生成的寄存器定义\n\n")
|
||||
|
||||
for _, row in df.iterrows():
|
||||
f.write(f"// {row['描述']}\n")
|
||||
f.write(f"#define {row['寄存器名']} ({row['地址']}UL)\n\n")
|
||||
|
||||
f.write("#endif // REGISTERS_H\n")
|
||||
print(f"生成头文件: {output_path}")
|
||||
|
||||
|
||||
def generate_test_code(df, output_path, header_name):
|
||||
"""生成自动化测试代码"""
|
||||
with open(output_path, 'w') as f:
|
||||
f.write("/**\n")
|
||||
f.write(" * 自动生成的寄存器测试代码\n")
|
||||
f.write(" */\n\n")
|
||||
f.write(f'#include "{header_name}"\n')
|
||||
f.write('#include <stdio.h>\n\n')
|
||||
|
||||
# 测试函数
|
||||
f.write("void test_registers(void)\n")
|
||||
f.write("{\n")
|
||||
|
||||
for _, row in df.iterrows():
|
||||
reg_name = row['寄存器名']
|
||||
addr = row['地址']
|
||||
reset_val = row.get('复位值', 0)
|
||||
|
||||
f.write(f" // 测试: {reg_name}\n")
|
||||
f.write(f' printf("[TEST] {reg_name}: 开始\\n");\n')
|
||||
|
||||
# 读取复位值测试
|
||||
f.write(f" uint32_t read_val = *(volatile uint32_t *){reg_name};\n")
|
||||
f.write(f' printf("[REG] {reg_name}: 复位值=0x%08X\\n", read_val);\n')
|
||||
|
||||
# 复位值验证
|
||||
f.write(f" if (read_val == {reset_val}UL) {{\n")
|
||||
f.write(f' printf("[PASS] {reg_name}: 复位值正确\\n");\n')
|
||||
f.write(" } else {\n")
|
||||
f.write(f' printf("[FAIL] {reg_name}: 期望值=0x{reset_val:08X}, 实际值=0x%08X\\n", read_val);\n')
|
||||
f.write(" }\n")
|
||||
|
||||
# 写读测试
|
||||
test_val = 0xAA55AA55
|
||||
f.write(f" // 写读测试\n")
|
||||
f.write(f" *(volatile uint32_t *){reg_name} = 0x{test_val:08X}UL;\n")
|
||||
f.write(f" read_val = *(volatile uint32_t *){reg_name};\n")
|
||||
f.write(f' printf("[REG] {reg_name}: 写入值=0x{test_val:08X}, 读回值=0x%08X\\n", read_val);\n')
|
||||
|
||||
f.write(f" if (read_val == 0x{test_val:08X}UL) {{\n")
|
||||
f.write(f' printf("[PASS] {reg_name}: 读写一致\\n");\n')
|
||||
f.write(" } else {\n")
|
||||
f.write(f' printf("[FAIL] {reg_name}: 期望值=0x{test_val:08X}, 实际值=0x%08X\\n", read_val);\n')
|
||||
f.write(" }\n")
|
||||
|
||||
f.write(f' printf("[TEST] {reg_name}: 结束\\n");\n')
|
||||
f.write(" printf(\"\\n\");\n\n")
|
||||
|
||||
f.write("}\n\n")
|
||||
|
||||
# 主函数
|
||||
f.write("int main(void)\n")
|
||||
f.write("{\n")
|
||||
f.write(' printf("=== 自动化寄存器测试开始 ===\\n\\n");\n')
|
||||
f.write(" test_registers();\n")
|
||||
f.write(' printf("\\n=== 自动化寄存器测试完成 ===\\n");\n')
|
||||
f.write(" while(1);\n")
|
||||
f.write(" return 0;\n")
|
||||
f.write("}\n")
|
||||
|
||||
print(f"生成测试代码: {output_path}")
|
||||
|
||||
|
||||
def generate_test_specs(df, output_path):
|
||||
"""生成测试描述文件 (JSON)"""
|
||||
specs = []
|
||||
for _, row in df.iterrows():
|
||||
spec = {
|
||||
'name': row['寄存器名'],
|
||||
'address': row['地址'],
|
||||
'description': row.get('描述', ''),
|
||||
'reset_value': row.get('复位值', 0),
|
||||
'test_cases': [
|
||||
'reset_value_check',
|
||||
'write_read_test'
|
||||
]
|
||||
}
|
||||
specs.append(spec)
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(specs, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"生成测试描述: {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register_dir = "docs/00_芯片资料/registers"
|
||||
|
||||
if os.path.exists(register_dir):
|
||||
for filename in os.listdir(register_dir):
|
||||
if filename.endswith(('.xlsx', '.xls')):
|
||||
file_path = os.path.join(register_dir, filename)
|
||||
df = parse_register_excel(file_path)
|
||||
|
||||
# 生成头文件
|
||||
header_name = filename.replace('.xlsx', '.h').replace('.xls', '.h')
|
||||
output_header = os.path.join("projects/P01_chip_test/inc", header_name)
|
||||
generate_header_file(df, output_header)
|
||||
|
||||
# 生成测试代码
|
||||
test_name = filename.replace('.xlsx', '_test.c').replace('.xls', '_test.c')
|
||||
output_test = os.path.join("projects/P01_chip_test/src", test_name)
|
||||
generate_test_code(df, output_test, header_name)
|
||||
|
||||
# 生成测试描述文件
|
||||
spec_name = filename.replace('.xlsx', '_spec.json').replace('.xls', '_spec.json')
|
||||
output_spec = os.path.join("projects/P01_chip_test/tests", spec_name)
|
||||
generate_test_specs(df, output_spec)
|
||||
else:
|
||||
print(f"目录不存在: {register_dir}")
|
||||
|
||||
Reference in New Issue
Block a user