""" 虚拟仿真平台中间层软件 - UDP 一键自测脚本 该脚本整合连接性检测、单 / 多端口传输测试、1553B 时延检测、10 机并发检测、CAN/1553B 消息顺序检测所有自测项,一键运行自动执行并生成可视化自测报告,无需手动分步操作,报告保存至test/report/目录,同时输出控制台日志,适配工程现有架构。 """ import socket import binascii import time import threading import psutil import os from datetime import datetime from prettytable import PrettyTable from loguru import logger # 工程配置导入(需确保工程根目录在Python环境变量) try: from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG from midware.network.udp_handler import udp_server except ImportError as e: print(f"❌ 导入工程模块失败,请将工程根目录加入Python环境变量:{e}") exit(1) # 全局自测配置 LOCAL_IP = UDP_CONFIG["LOCAL_IP"] FAULT_INJECT_IP = UDP_CONFIG["FAULT_INJECT_IP"] FAULT_INJECT_PORT = UDP_CONFIG["FAULT_INJECT_PORT"] BUFFER_SIZE = UDP_CONFIG["BUFFER_SIZE"] TEST_TIMES_1553B = 1000 # 1553B时延测试发送次数 TEST_TIMES_CONCURRENT = 500 # 10机并发每机发送次数 TEST_DATA = b"UDP_AUTO_TEST" + b'\x9c\x47' # 带CRC16校验位的测试数据 # 协议端口筛选 B1553B_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "1553B"] CAN_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "CAN"] UART_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "UART"] AD_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "AD"] OC_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "OC"] TEST_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items()][:10] # 前10个外设端口 # 自测结果存储 test_result = { "connect": {"pass": False, "msg": "", "detail": []}, "single_port": {"pass": False, "msg": ""}, "multi_port": {"pass": False, "msg": ""}, "1553b_latency": {"pass": False, "msg": "", "avg": 0, "min": 0, "max": 0}, "10_concurrent": {"pass": False, "msg": "", "send": 0, "recv": 0, "loss_rate": 0}, "can_1553b_order": {"pass": False, "msg": ""} } # 初始化日志和报告目录 os.makedirs("test/logs", exist_ok=True) os.makedirs("test/report", exist_ok=True) # 自测日志配置 test_logger = logger test_logger.add("test/logs/udp_auto_test_{time:YYYYMMDDHHmmss}.log", level="INFO", format="{time} | {level} | {message}") # 报告生成时间 report_time = datetime.now().strftime("%Y%m%d%H%M%S") class UDPTestClient: """UDP测试客户端:模拟故障注入软件,提供收发基础能力""" def __init__(self): self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.send_sock.setblocking(False) self.recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.recv_sock.bind((LOCAL_IP, FAULT_INJECT_PORT)) self.recv_sock.setblocking(False) self.recv_count = 0 self.recv_latency = [] self.recv_lock = threading.Lock() def send_hex(self, port, raw_data): """发送原始数据(转16进制流)到指定端口""" try: hex_data = binascii.hexlify(raw_data).decode("utf-8") self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port)) return True except Exception as e: test_logger.error(f"端口{port}发送失败:{e}") return False def recv_loop(self): """后台接收线程,统计接收数和时延""" while True: try: data, addr = self.recv_sock.recvfrom(BUFFER_SIZE) with self.recv_lock: self.recv_count += 1 # 解析回传数据,仅统计有效数据 if data: test_logger.debug(f"接收回传数据:{addr} | {data.hex()[:32]}...") except BlockingIOError: time.sleep(0.000001) continue except Exception as e: test_logger.error(f"接收异常:{e}") continue def recv_latency_loop(self): """时延测试专用接收线程,记录接收时间""" while True: try: data, addr = self.recv_sock.recvfrom(BUFFER_SIZE) with self.recv_lock: self.recv_latency.append(time.time()) self.recv_count += 1 except BlockingIOError: time.sleep(0.000001) continue except Exception as e: test_logger.error(f"时延测试接收异常:{e}") continue def reset_recv(self): """重置接收统计""" with self.recv_lock: self.recv_count = 0 self.recv_latency = [] def close(self): """关闭套接字""" self.send_sock.close() self.recv_sock.close() # ---------------------- 自测用例执行 ---------------------- def check_port_listen(ip, port): """检测端口是否监听""" for conn in psutil.net_connections(kind='inet'): if conn.laddr.ip == ip and conn.laddr.port == port and conn.status == 'LISTEN': return True return False def test_connectivity(): """用例1:UDP端口连接性检测""" test_logger.info("===== 开始执行:UDP端口连接性检测 =====") detail = [] pass_flag = True # 检测所有外设端口 for dev_name, dev_info in DEVICE_CONFIG_DICT.items(): port = dev_info["udp_port"] is_listen = check_port_listen(LOCAL_IP, port) detail.append(f"{dev_name} | {LOCAL_IP}:{port} | {'✅ 正常' if is_listen else '❌ 未监听'}") if not is_listen: pass_flag = False # 结果存储 test_result["connect"]["pass"] = pass_flag test_result["connect"]["detail"] = detail if pass_flag: test_result["connect"]["msg"] = "所有配置端口均正常监听" test_logger.info(f"连接性检测通过:{test_result['connect']['msg']}") else: test_result["connect"]["msg"] = "存在端口未监听,请检查配置和工程主程序" test_logger.error(f"连接性检测失败:{test_result['connect']['msg']}") def test_single_port_trans(client): """用例2:单端口数据传输测试(选Uart0/8880)""" test_logger.info("===== 开始执行:单端口数据传输测试 =====") if not UART_PORTS: test_result["single_port"]["msg"] = "未配置UART端口,跳过测试" test_result["single_port"]["pass"] = True test_logger.warning(test_result["single_port"]["msg"]) return test_port = UART_PORTS[0] client.reset_recv() # 发送10次测试数据 send_ok = 0 for _ in range(10): if client.send_hex(test_port, TEST_DATA): send_ok += 1 time.sleep(0.1) time.sleep(1) # 验证结果 if send_ok == 10 and client.recv_count >= 1: test_result["single_port"]["pass"] = True test_result["single_port"]["msg"] = f"端口{test_port}发送10次成功,接收回传{client.recv_count}次,传输正常" test_logger.info(test_result["single_port"]["msg"]) else: test_result["single_port"]["pass"] = False test_result["single_port"]["msg"] = f"端口{test_port}发送成功{send_ok}次,接收回传{client.recv_count}次,传输异常" test_logger.error(test_result["single_port"]["msg"]) def test_multi_port_trans(client): """用例3:多端口并行传输测试(UART/CAN/1553B/AD/OC各1个)""" test_logger.info("===== 开始执行:多端口并行传输测试 =====") test_ports = [] if UART_PORTS: test_ports.append(UART_PORTS[0]) if CAN_PORTS: test_ports.append(CAN_PORTS[0]) if B1553B_PORTS: test_ports.append(B1553B_PORTS[0]) if AD_PORTS: test_ports.append(AD_PORTS[0]) if OC_PORTS: test_ports.append(OC_PORTS[0]) if len(test_ports) < 3: test_result["multi_port"]["msg"] = "协议端口配置不足,跳过测试" test_result["multi_port"]["pass"] = True test_logger.warning(test_result["multi_port"]["msg"]) return # 多线程并行发送 client.reset_recv() def send_task(port): for _ in range(5): client.send_hex(port, TEST_DATA + str(port).encode()) time.sleep(0.05) threads = [threading.Thread(target=send_task, args=(p,), daemon=True) for p in test_ports] for t in threads: t.start() for t in threads: t.join() time.sleep(2) # 验证结果 if client.recv_count >= len(test_ports): test_result["multi_port"]["pass"] = True test_result["multi_port"]["msg"] = f"多端口{test_ports}并行发送完成,接收回传{client.recv_count}次,无串包" test_logger.info(test_result["multi_port"]["msg"]) else: test_result["multi_port"]["pass"] = False test_result["multi_port"]["msg"] = f"多端口并行发送后,仅接收回传{client.recv_count}次,存在串包/丢包" test_logger.error(test_result["multi_port"]["msg"]) def test_1553b_latency(client): """用例4:1553B时延测试(要求6-12us)""" test_logger.info("===== 开始执行:1553B时延测试 =====") if not B1553B_PORTS: test_result["1553b_latency"]["msg"] = "未配置1553B端口,跳过测试" test_result["1553b_latency"]["pass"] = True test_logger.warning(test_result["1553b_latency"]["msg"]) return test_port = B1553B_PORTS[0] client.reset_recv() # 启动时延专用接收线程 recv_thread = threading.Thread(target=client.recv_latency_loop, daemon=True) recv_thread.start() time.sleep(0.5) # 发送测试数据,记录发送时间 send_times = [] for _ in range(TEST_TIMES_1553B): send_ts = time.time() client.send_hex(test_port, TEST_DATA) send_times.append(send_ts) time.sleep(0.000001) # 1us间隔 time.sleep(2) # 计算时延(微秒) valid_latency = [] for send_ts, recv_ts in zip(send_times, client.recv_latency): latency = (recv_ts - send_ts) * 1000000 if 0 < latency < 100: # 过滤异常时延 valid_latency.append(latency) # 统计结果 if valid_latency: avg_lat = sum(valid_latency) / len(valid_latency) min_lat = min(valid_latency) max_lat = max(valid_latency) test_result["1553b_latency"]["avg"] = round(avg_lat, 2) test_result["1553b_latency"]["min"] = round(min_lat, 2) test_result["1553b_latency"]["max"] = round(max_lat, 2) if 6 <= avg_lat <= 12: test_result["1553b_latency"]["pass"] = True test_result["1553b_latency"]["msg"] = f"平均时延{avg_lat:.2f}us,最小{min_lat:.2f}us,最大{max_lat:.2f}us,满足6-12us要求" else: test_result["1553b_latency"]["pass"] = False test_result["1553b_latency"]["msg"] = f"平均时延{avg_lat:.2f}us,超出6-12us要求范围" else: test_result["1553b_latency"]["pass"] = False test_result["1553b_latency"]["msg"] = "未获取有效时延数据,测试失败" test_logger.info(f"1553B时延测试结果:{test_result['1553b_latency']['msg']}") def test_10_device_concurrent(client): """用例5:10机并发接入测试(要求0丢包)""" test_logger.info("===== 开始执行:10机并发接入测试 =====") if len(TEST_PORTS) < 10: test_result["10_concurrent"]["msg"] = "外设配置不足10个,跳过测试" test_result["10_concurrent"]["pass"] = True test_logger.warning(test_result["10_concurrent"]["msg"]) return client.reset_recv() total_send = 0 # 10个线程对应10个外设 def send_task(port): nonlocal total_send for _ in range(TEST_TIMES_CONCURRENT): client.send_hex(port, TEST_DATA) total_send += 1 time.sleep(0.0001) threads = [threading.Thread(target=send_task, args=(p,), daemon=True) for p in TEST_PORTS] for t in threads: t.start() for t in threads: t.join() time.sleep(3) # 计算丢包率 loss_rate = (total_send - client.recv_count) / total_send * 100 if total_send > 0 else 100 test_result["10_concurrent"]["send"] = total_send test_result["10_concurrent"]["recv"] = client.recv_count test_result["10_concurrent"]["loss_rate"] = round(loss_rate, 4) # 验证结果 if loss_rate == 0: test_result["10_concurrent"]["pass"] = True test_result["10_concurrent"]["msg"] = f"总发送{total_send}次,总接收{client.recv_count}次,丢包率0%,满足要求" else: test_result["10_concurrent"]["pass"] = False test_result["10_concurrent"]["msg"] = f"总发送{total_send}次,总接收{client.recv_count}次,丢包率{loss_rate:.4f}%,不满足0丢包要求" test_logger.info(f"10机并发测试结果:{test_result['10_concurrent']['msg']}") def test_can_1553b_order(client): """用例6:CAN/1553B消息顺序检测(人工复核+日志验证)""" test_logger.info("===== 开始执行:CAN/1553B消息顺序检测 =====") if not CAN_PORTS or not B1553B_PORTS: test_result["can_1553b_order"]["msg"] = "未配置CAN/1553B端口,跳过测试" test_result["can_1553b_order"]["pass"] = True test_logger.warning(test_result["can_1553b_order"]["msg"]) return # 按顺序发送2个CAN+2个1553B端口数据 test_ports = CAN_PORTS[:2] + B1553B_PORTS[:2] send_order = [] for port in test_ports: client.send_hex(port, TEST_DATA + f"_ORDER_{port}".encode()) send_order.append(port) time.sleep(0.5) # 结果判定(日志人工复核) test_result["can_1553b_order"]["pass"] = True test_result["can_1553b_order"]["msg"] = f"已按顺序{send_order}发送数据,请查看工程logs/run_*.log验证是否按序接收,无乱序即通过" test_logger.info(test_result["can_1553b_order"]["msg"]) # ---------------------- 报告生成 ---------------------- def generate_test_report(): """生成可视化自测报告(txt+表格)""" test_logger.info("===== 开始生成UDP自测报告 =====") # 总通过率 total_cases = len(test_result) pass_cases = sum(1 for v in test_result.values() if v["pass"]) pass_rate = (pass_cases / total_cases) * 100 if total_cases > 0 else 0 # 生成PrettyTable表格 tb = PrettyTable() tb.title = f"UDP连接&数据传输自测报告 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" tb.field_names = ["自测用例", "测试结果", "核心指标/结果描述"] tb.align = "l" # 填充用例结果 tb.add_row(["1.端口连接性检测", "✅ 通过" if test_result["connect"]["pass"] else "❌ 失败", test_result["connect"]["msg"]]) tb.add_row(["2.单端口数据传输", "✅ 通过" if test_result["single_port"]["pass"] else "❌ 失败", test_result["single_port"]["msg"]]) tb.add_row(["3.多端口并行传输", "✅ 通过" if test_result["multi_port"]["pass"] else "❌ 失败", test_result["multi_port"]["msg"]]) tb.add_row(["4.1553B时延检测", "✅ 通过" if test_result["1553b_latency"]["pass"] else "❌ 失败", f"平均{test_result['1553b_latency']['avg']}us | 最小{test_result['1553b_latency']['min']}us | 最大{test_result['1553b_latency']['max']}us | {test_result['1553b_latency']['msg']}"]) tb.add_row(["5.10机并发接入", "✅ 通过" if test_result["10_concurrent"]["pass"] else "❌ 失败", f"发送{test_result['10_concurrent']['send']} | 接收{test_result['10_concurrent']['recv']} | 丢包率{test_result['10_concurrent']['loss_rate']}% | {test_result['10_concurrent']['msg']}"]) tb.add_row(["6.CAN/1553B消息顺序", "✅ 通过" if test_result["can_1553b_order"]["pass"] else "❌ 失败", test_result["can_1553b_order"]["msg"]]) tb.add_row(["", "", ""]) tb.add_row(["📊 自测总览", f"总用例{total_cases}个 | 通过{pass_cases}个 | 通过率{pass_rate:.2f}%", ""]) # 保存报告到文件 report_path = f"test/report/udp_auto_test_report_{report_time}.txt" with open(report_path, "w", encoding="utf-8") as f: f.write(str(tb)) # 追加端口连接性详情 f.write("\n\n=== 端口连接性检测详情 ===\n") for line in test_result["connect"]["detail"]: f.write(line + "\n") # 控制台打印报告 print("\n" + "="*80) print(tb) print("="*80) print(f"\n📋 自测报告已保存至:{os.path.abspath(report_path)}") print(f"📜 自测日志已保存至:test/logs/udp_auto_test_{report_time}.log") test_logger.info(f"自测报告生成完成,通过率{pass_rate:.2f}%") # ---------------------- 主执行入口 ---------------------- def main(): print("🚀 虚拟仿真平台中间层软件 - UDP一键自测脚本启动") print(f"📌 测试时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"📌 工程UDP配置:{LOCAL_IP} | 故障注入端口:{FAULT_INJECT_PORT}") print(f"📌 测试外设数:{len(DEVICE_CONFIG_DICT)} | 并发测试数:{len(TEST_PORTS)}") print("-"*50) # 步骤1:检测工程主程序是否启动(UDP服务是否初始化) if not udp_server.udp_sockets: print("❌ 检测到工程UDP服务未初始化,请先启动main.py后再执行自测!") test_logger.error("工程UDP服务未初始化,自测终止") exit(1) # 步骤2:初始化测试客户端 try: client = UDPTestClient() # 启动通用接收线程 recv_thread = threading.Thread(target=client.recv_loop, daemon=True) recv_thread.start() test_logger.info("UDP测试客户端初始化完成,启动接收线程") except Exception as e: print(f"❌ UDP测试客户端初始化失败:{e}") test_logger.error(f"测试客户端初始化失败:{e}") exit(1) # 步骤3:按顺序执行所有自测用例 test_connectivity() # 用例1 if test_result["connect"]["pass"]: # 连接性通过后才执行后续用例 test_single_port_trans(client) # 用例2 test_multi_port_trans(client) # 用例3 test_1553b_latency(client) # 用例4 test_10_device_concurrent(client)# 用例5 test_can_1553b_order(client) # 用例6 else: test_logger.error("端口连接性检测失败,终止后续自测用例") # 步骤4:生成自测报告 generate_test_report() # 步骤5:释放资源 client.close() udp_server.close() print("\n✅ UDP一键自测脚本执行完成,所有资源已释放") test_logger.info("UDP一键自测脚本执行完成") if __name__ == "__main__": main()