first commit
This commit is contained in:
22
tests/test_udp_connect.py
Normal file
22
tests/test_udp_connect.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import socket
|
||||
import psutil
|
||||
from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||
|
||||
def check_port_listen(ip, port):
|
||||
"""检测指定IP:端口是否被监听"""
|
||||
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
|
||||
|
||||
if __name__ == "__main__":
|
||||
local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||
# 检测所有外设UDP端口
|
||||
print("===== UDP端口监听状态检测 =====")
|
||||
for dev_name, dev_info in DEVICE_CONFIG_DICT.items():
|
||||
port = dev_info["udp_port"]
|
||||
is_listen = check_port_listen(local_ip, port)
|
||||
status = "✅ 正常监听" if is_listen else "❌ 未监听"
|
||||
print(f"{dev_name} | {local_ip}:{port} | {status}")
|
||||
# 检测故障注入软件对接端口(非监听,为发送套接字)
|
||||
print(f"\n故障注入软件接收端口:{local_ip}:{UDP_CONFIG['FAULT_INJECT_PORT']}(自测客户端监听)")
|
||||
295
tests/test_udp_connect_2.py
Normal file
295
tests/test_udp_connect_2.py
Normal file
@@ -0,0 +1,295 @@
|
||||
# 注释掉/删除Linux的shebang行,替换为Windows兼容的写法(可选)
|
||||
# #!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
故障注入平台UDP模拟客户端
|
||||
功能:向中间件多UDP端口发送16进制流数据,模拟故障注入平台的发包行为
|
||||
作者:工程测试专用
|
||||
日期:2026-02-26
|
||||
"""
|
||||
"""
|
||||
该程序专门模拟故障注入平台的 UDP 数据发送行为,向中间件的多 UDP 端口发送符合协议格式的 16 进制流数据,可验证中间件udp_handler.py的多端口非阻塞接收、数据解析、端口隔离等核心功能,支持单端口定向发送、多端口并发发送、自定义数据格式,全程适配工程的 16 进制流数据规范。
|
||||
一、测试程序核心功能
|
||||
模拟故障注入平台向中间件指定 UDP 端口发送 16 进制流数据;
|
||||
支持单端口循环发送、多端口并发发送(验证端口隔离性);
|
||||
可自定义测试数据(原始二进制 / 16 进制字符串),自动适配工程数据格式;
|
||||
实时打印发送日志,包含目标端口、数据长度、发送状态;
|
||||
支持按协议类型(UART/CAN/1553B/AD/OC)批量发送对应外设数据。
|
||||
"""
|
||||
"""
|
||||
使用步骤
|
||||
步骤 1:配置适配
|
||||
修改DEVICE_UDP_PORTS字典,确保与中间件device_config.csv中的外设名称 + UDP 端口完全一致;
|
||||
确认MIDDLEWARE_IP为中间件运行的 IP(本地测试填127.0.0.1,跨机器测试填中间件所在机器 IP);
|
||||
调整TEST_CONFIG中的参数:
|
||||
send_interval:发送间隔(秒),默认 1ms,模拟实时数据;
|
||||
concurrent_threads:并发线程数,默认 5,可根据机器性能调整;
|
||||
send_times_per_port:每个端口发送次数,默认 1000 次。
|
||||
步骤 2:运行准备
|
||||
先启动中间件主程序main.py,确保:
|
||||
配置初始化成功(日志显示 “加载 X 个外设”);
|
||||
UDP 服务初始化完成(日志显示 “监听端口:[8880,8881,...]”);
|
||||
安装依赖(仅需 loguru):
|
||||
bash
|
||||
运行
|
||||
pip install loguru
|
||||
步骤 3:执行测试
|
||||
将测试程序保存为fault_inject_simulator.py,放在工程根目录;
|
||||
运行测试程序:
|
||||
bash
|
||||
运行
|
||||
python fault_inject_simulator.py
|
||||
可选测试模式:
|
||||
单端口测试:取消send_single_port_cycle注释,测试指定外设端口的接收能力;
|
||||
多端口并发测试:取消send_multi_port_concurrent注释,测试多端口隔离性和并发接收能力。
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
|
||||
# 设置标准输出和标准错误的编码为UTF-8
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
import socket
|
||||
import binascii
|
||||
import time
|
||||
import threading
|
||||
import random
|
||||
from loguru import logger
|
||||
|
||||
from pathlib import Path
|
||||
# 获取当前文件的目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 获取上级目录(项目根目录)
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
# 将项目根目录添加到系统路径
|
||||
sys.path.append(parent_dir)
|
||||
#sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
from midware.config.base_config import UDP_CONFIG
|
||||
|
||||
# ==================== 配置项(与中间件base_config.py保持一致)====================
|
||||
# 中间件本地IP(故障注入平台访问的IP)
|
||||
MIDDLEWARE_IP = "127.0.0.1"
|
||||
# 故障注入平台接收端口(中间件回传数据的端口,可选)
|
||||
FAULT_RECV_PORT = 8889
|
||||
# 中间件监听的外设UDP端口列表(从device_config.csv复制)
|
||||
DEVICE_UDP_PORTS = {
|
||||
"Uart0": 4000,
|
||||
"CAN0": 4001,#": 8881,
|
||||
# "1553B0": 8882,
|
||||
# "AD0": 8883,
|
||||
# "OC0": 8884,
|
||||
# "Uart1": 8885,
|
||||
# "CAN2": 8886,
|
||||
# "1553B1": 8887,
|
||||
# "AD1": 8888,
|
||||
# "OC1": 8889
|
||||
}
|
||||
# 测试数据配置
|
||||
TEST_CONFIG = {
|
||||
"buffer_size": 32, # UDP缓冲区大小,4096
|
||||
"send_interval": 0.1, # 单端口发送间隔(秒,1ms,模拟实时数据)
|
||||
"concurrent_threads": 5, # 并发发送线程数
|
||||
"send_times_per_port": 10 # 每个端口循环发送次数
|
||||
}
|
||||
|
||||
# ==================== 日志配置 ====================
|
||||
logger.add("fault_inject_simulator.log", level="INFO",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
|
||||
rotation="10MB", retention="3 days")
|
||||
|
||||
class FaultInjectUDPSimulator:
|
||||
"""故障注入平台UDP模拟客户端"""
|
||||
def __init__(self):
|
||||
# 创建非阻塞UDP发送套接字
|
||||
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.send_sock.setblocking(False)
|
||||
# 开启地址复用,避免端口占用
|
||||
self.send_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# 发送统计
|
||||
self.send_stats = {
|
||||
"total_send": 0,
|
||||
"success_send": 0,
|
||||
"failed_send": 0,
|
||||
"port_stats": {} # 按端口统计:{port: {"success":0, "failed":0}}
|
||||
}
|
||||
# 初始化端口统计
|
||||
for port in DEVICE_UDP_PORTS.values():
|
||||
self.send_stats["port_stats"][port] = {"success": 0, "failed": 0}
|
||||
logger.info("故障注入平台UDP模拟器初始化完成")
|
||||
|
||||
def _raw_to_hex_str(self, raw_data: bytes) -> str:
|
||||
"""
|
||||
原始二进制数据转16进制字符串(符合中间件数据格式要求)
|
||||
:param raw_data: 原始二进制数据
|
||||
:return: 16进制字符串(如b"test" → "74657374")
|
||||
"""
|
||||
return binascii.hexlify(raw_data).decode("utf-8")
|
||||
|
||||
def send_data_to_port(self, port: int, raw_data: bytes, dev_name: str = "unknown"):
|
||||
"""
|
||||
向指定UDP端口发送数据(核心方法)
|
||||
:param port: 中间件监听的UDP端口
|
||||
:param raw_data: 原始二进制数据(自动转为16进制流)
|
||||
:param dev_name: 外设名称(用于日志)
|
||||
"""
|
||||
try:
|
||||
# 转换为16进制字符串(中间件要求的格式)
|
||||
hex_data = self._raw_to_hex_str(raw_data)
|
||||
# 非阻塞发送数据
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (MIDDLEWARE_IP, port))
|
||||
# 更新统计
|
||||
self.send_stats["total_send"] += 1
|
||||
self.send_stats["success_send"] += 1
|
||||
self.send_stats["port_stats"][port]["success"] += 1
|
||||
logger.debug(
|
||||
f"发送成功 | 外设:{dev_name} | 端口:{port} | "
|
||||
f"原始数据长度:{len(raw_data)}字节 | 16进制流:{hex_data[:32]}..."
|
||||
)
|
||||
return True
|
||||
except BlockingIOError:
|
||||
# 非阻塞发送缓冲区满
|
||||
self.send_stats["total_send"] += 1
|
||||
self.send_stats["failed_send"] += 1
|
||||
self.send_stats["port_stats"][port]["failed"] += 1
|
||||
logger.warning(f"发送失败 | 外设:{dev_name} | 端口:{port} | 原因:发送缓冲区满")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.send_stats["total_send"] += 1
|
||||
self.send_stats["failed_send"] += 1
|
||||
self.send_stats["port_stats"][port]["failed"] += 1
|
||||
logger.error(
|
||||
f"发送异常 | 外设:{dev_name} | 端口:{port} | 错误:{str(e)}",
|
||||
exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
def send_single_port_cycle(self, dev_name: str, send_times: int = None):
|
||||
"""
|
||||
单端口循环发送测试数据
|
||||
:param dev_name: 外设名称(如"Uart0",对应DEVICE_UDP_PORTS中的key)
|
||||
:param send_times: 发送次数,默认使用TEST_CONFIG中的配置
|
||||
"""
|
||||
if dev_name not in DEVICE_UDP_PORTS:
|
||||
logger.error(f"外设{dev_name}不存在,可选外设:{list(DEVICE_UDP_PORTS.keys())}")
|
||||
return
|
||||
port = DEVICE_UDP_PORTS[dev_name]
|
||||
send_times = send_times or TEST_CONFIG["send_times_per_port"]
|
||||
logger.info(f"开始单端口循环发送 | 外设:{dev_name} | 端口:{port} | 发送次数:{send_times}")
|
||||
|
||||
# 按协议类型生成测试数据(模拟真实外设数据)
|
||||
if "UART" in dev_name:
|
||||
raw_data = b"UART_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x9c\x47" # 带CRC16
|
||||
elif "CAN" in dev_name:
|
||||
raw_data = b"CAN_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x1a\x2b" # 带CRC16
|
||||
elif "1553B" in dev_name:
|
||||
raw_data = b"1553B_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x0f\x3d" # 带CRC16
|
||||
elif "AD" in dev_name:
|
||||
raw_data = b"AD_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x5f" # 带累加和
|
||||
elif "OC" in dev_name:
|
||||
raw_data = b"OC_DATA_" + str(random.randint(1000, 9999)).encode() + b"\x6a" # 带累加和
|
||||
else:
|
||||
raw_data = b"TEST_DATA_" + str(random.randint(1000, 9999)).encode()
|
||||
|
||||
# 循环发送
|
||||
for i in range(send_times):
|
||||
self.send_data_to_port(port, raw_data, dev_name)
|
||||
time.sleep(TEST_CONFIG["send_interval"])
|
||||
logger.info(f"单端口发送完成 | 外设:{dev_name} | 端口:{port} | 发送统计:{self.send_stats['port_stats'][port]}")
|
||||
|
||||
def send_multi_port_concurrent(self, dev_names: list = None, send_times: int = None):
|
||||
"""
|
||||
多端口并发发送测试(验证端口隔离性)
|
||||
:param dev_names: 外设名称列表,默认发送所有外设
|
||||
:param send_times: 每个端口发送次数,默认使用TEST_CONFIG中的配置
|
||||
"""
|
||||
dev_names = dev_names or list(DEVICE_UDP_PORTS.keys())
|
||||
send_times = send_times or TEST_CONFIG["send_times_per_port"]
|
||||
logger.info(f"开始多端口并发发送 | 外设列表:{dev_names} | 每端口发送次数:{send_times}")
|
||||
|
||||
# 定义单个端口的发送任务
|
||||
def send_task(dev_name):
|
||||
port = DEVICE_UDP_PORTS[dev_name]
|
||||
# 生成对应协议的测试数据
|
||||
if "UART" in dev_name:
|
||||
raw_data = b"UART_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||
elif "CAN" in dev_name:
|
||||
raw_data = b"CAN_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||
elif "1553B" in dev_name:
|
||||
raw_data = b"1553B_CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||
else:
|
||||
raw_data = b"CONCURRENT_" + dev_name.encode() + b"_" + str(random.randint(1000, 9999)).encode()
|
||||
# 循环发送
|
||||
for i in range(send_times):
|
||||
self.send_data_to_port(port, raw_data, dev_name)
|
||||
time.sleep(TEST_CONFIG["send_interval"])
|
||||
|
||||
# 创建并发线程
|
||||
threads = []
|
||||
max_threads = TEST_CONFIG["concurrent_threads"]
|
||||
active_threads = 0
|
||||
for dev_name in dev_names:
|
||||
if dev_name not in DEVICE_UDP_PORTS:
|
||||
logger.warning(f"外设{dev_name}不存在,跳过")
|
||||
continue
|
||||
# 控制并发线程数,避免资源耗尽
|
||||
while active_threads >= max_threads:
|
||||
time.sleep(0.001)
|
||||
active_threads = sum(1 for t in threads if t.is_alive())
|
||||
# 启动线程
|
||||
t = threading.Thread(target=send_task, args=(dev_name,), daemon=True)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
active_threads += 1
|
||||
logger.debug(f"启动并发发送线程 | 外设:{dev_name} | 线程ID:{t.ident}")
|
||||
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
logger.info("多端口并发发送完成 | 全局统计:"
|
||||
f"总发送{self.send_stats['total_send']} | "
|
||||
f"成功{self.send_stats['success_send']} | "
|
||||
f"失败{self.send_stats['failed_send']}")
|
||||
|
||||
def print_send_stats(self):
|
||||
"""打印最终发送统计"""
|
||||
logger.info("="*50 + " 发送统计汇总 " + "="*50)
|
||||
logger.info(f"全局统计 | 总发送:{self.send_stats['total_send']} | 成功:{self.send_stats['success_send']} | 失败:{self.send_stats['failed_send']}")
|
||||
logger.info("端口统计:")
|
||||
for port, stats in self.send_stats["port_stats"].items():
|
||||
dev_name = [k for k, v in DEVICE_UDP_PORTS.items() if v == port][0]
|
||||
total = stats["success"] + stats["failed"]
|
||||
success_rate = (stats["success"] / total * 100) if total > 0 else 0
|
||||
logger.info(f" 外设:{dev_name:6s} | 端口:{port:5d} | 发送:{total:5d} | 成功:{stats['success']:5d} | 失败:{stats['failed']:5d} | 成功率:{success_rate:.2f}%")
|
||||
logger.info("="*100)
|
||||
|
||||
def close(self):
|
||||
"""关闭套接字,释放资源"""
|
||||
self.send_sock.close()
|
||||
logger.info("故障注入平台UDP模拟器已关闭,资源释放完成")
|
||||
|
||||
# ==================== 测试执行入口 ====================
|
||||
def main():
|
||||
# 初始化模拟器
|
||||
simulator = FaultInjectUDPSimulator()
|
||||
print("故障注入平台UDP模拟器已启动,按Ctrl+C终止测试")
|
||||
try:
|
||||
# 可选测试模式:二选一或全选
|
||||
# 模式1:单端口循环发送(测试Uart0)
|
||||
# simulator.send_single_port_cycle("Uart0", send_times=50)
|
||||
|
||||
# 模式2:多端口并发发送(测试所有外设)
|
||||
simulator.send_multi_port_concurrent(dev_names=["Uart0", "CAN0", "1553B0", "AD0", "OC0"], send_times=20)
|
||||
|
||||
# 打印统计结果
|
||||
simulator.print_send_stats()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("用户终止测试,打印最终统计...")
|
||||
simulator.print_send_stats()
|
||||
finally:
|
||||
# 关闭模拟器
|
||||
simulator.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
148
tests/test_udp_performance.py
Normal file
148
tests/test_udp_performance.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
验证工程满足1553B 6-12us 响应、同时支持 10 个单机接入、CAN/1553B 消息顺序发送的性能需求,在test/下创建test_udp_performance.py
|
||||
2026/2/26
|
||||
Tan Mingyan
|
||||
"""
|
||||
|
||||
|
||||
import socket
|
||||
import binascii
|
||||
import time
|
||||
import threading
|
||||
from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
|
||||
|
||||
# 全局配置
|
||||
LOCAL_IP = UDP_CONFIG["LOCAL_IP"]
|
||||
FAULT_PORT = UDP_CONFIG["FAULT_INJECT_PORT"]
|
||||
BUFFER_SIZE = 4096
|
||||
# 测试用1553B外设端口(取CSV中的1553B0/1553B1,如8882/8886)
|
||||
B1553B_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "1553B"]
|
||||
# CAN外设端口
|
||||
CAN_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items() if dev_info["protocol"] == "CAN"]
|
||||
# 10个测试端口(覆盖所有协议)
|
||||
TEST_PORTS = [dev_info["udp_port"] for dev_name, dev_info in DEVICE_CONFIG_DICT.items()][:10]
|
||||
# 测试数据(小数据包,模拟星务平台实时数据)
|
||||
TEST_DATA = b"PERF_TEST" + b'\x9c\x47' # 含校验位
|
||||
|
||||
class UDPPerformanceTest:
|
||||
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_PORT))
|
||||
self.recv_sock.setblocking(False)
|
||||
# 时延统计
|
||||
self.latency_list = []
|
||||
# 并发计数
|
||||
self.send_count = 0
|
||||
self.recv_count = 0
|
||||
|
||||
def _calc_latency(self, port, send_ts):
|
||||
"""计算单包时延:发送时间→接收回传时间"""
|
||||
try:
|
||||
data, _ = self.recv_sock.recvfrom(BUFFER_SIZE)
|
||||
recv_ts = time.time()
|
||||
latency = (recv_ts - send_ts) * 1000000 # 转换为微秒(us)
|
||||
self.latency_list.append(latency)
|
||||
self.recv_count += 1
|
||||
print(f"1553B端口{port} | 响应时延:{latency:.2f}us | 累计接收:{self.recv_count}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def test_1553b_latency(self, test_times=1000):
|
||||
"""测试1553B响应时延(要求6-12us),发送test_times次数据包"""
|
||||
if not B1553B_PORTS:
|
||||
print("❌ 未配置1553B外设,跳过时延测试")
|
||||
return
|
||||
test_port = B1553B_PORTS[0]
|
||||
print(f"===== 1553B时延测试(端口{test_port},发送{test_times}次) =====")
|
||||
self.latency_list.clear()
|
||||
self.recv_count = 0
|
||||
|
||||
for i in range(test_times):
|
||||
send_ts = time.time()
|
||||
# 发送数据
|
||||
hex_data = binascii.hexlify(TEST_DATA).decode("utf-8")
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, test_port))
|
||||
# 立即统计时延(模拟中间层实时回传)
|
||||
self._calc_latency(test_port, send_ts)
|
||||
# 微秒级间隔,模拟星务平台请求频率
|
||||
time.sleep(0.000001)
|
||||
|
||||
# 统计结果
|
||||
if self.latency_list:
|
||||
avg_latency = sum(self.latency_list) / len(self.latency_list)
|
||||
min_latency = min(self.latency_list)
|
||||
max_latency = max(self.latency_list)
|
||||
print(f"\n1553B时延统计 | 平均:{avg_latency:.2f}us | 最小:{min_latency:.2f}us | 最大:{max_latency:.2f}us")
|
||||
if avg_latency >=6 and avg_latency <=12:
|
||||
print("✅ 1553B时延满足要求(6-12us)")
|
||||
else:
|
||||
print("❌ 1553B时延不满足要求")
|
||||
|
||||
def test_10_device_concurrent(self, test_times=500):
|
||||
"""测试10个单机并发接入(同时发送,验证无丢包)"""
|
||||
if len(TEST_PORTS) <10:
|
||||
print("❌ 未配置10个外设,跳过多机并发测试")
|
||||
return
|
||||
print(f"===== 10机并发测试(发送{test_times}次/机) =====")
|
||||
self.send_count = 0
|
||||
self.recv_count = 0
|
||||
|
||||
def send_task(port):
|
||||
for _ in range(test_times):
|
||||
hex_data = binascii.hexlify(TEST_DATA).decode("utf-8")
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port))
|
||||
self.send_count +=1
|
||||
time.sleep(0.0001)
|
||||
|
||||
# 启动10个线程,对应10个外设
|
||||
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)
|
||||
loss_rate = (self.send_count - self.recv_count) / self.send_count * 100
|
||||
print(f"总发送:{self.send_count} | 总接收:{self.recv_count} | 丢包率:{loss_rate:.2f}%")
|
||||
if loss_rate == 0:
|
||||
print("✅ 10机并发测试通过,无丢包")
|
||||
else:
|
||||
print("❌ 10机并发测试失败,存在丢包")
|
||||
|
||||
def test_can_b1553b_order(self):
|
||||
"""测试CAN/1553B消息顺序发送(要求一个节点发完再发下一个)"""
|
||||
print("===== CAN/1553B消息顺序测试 =====")
|
||||
# 按顺序发送多个节点数据,查看中间层日志
|
||||
test_ports = CAN_PORTS[:2] + B1553B_PORTS[:2]
|
||||
for port in test_ports:
|
||||
hex_data = binascii.hexlify(TEST_DATA + str(port).encode()).decode("utf-8")
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (LOCAL_IP, port))
|
||||
print(f"按顺序发送:端口{port} 数据标识:{port}")
|
||||
time.sleep(0.001)
|
||||
print("✅ 请查看工程logs/run_*.log,验证日志中节点数据是否按发送顺序打印(无乱序)")
|
||||
|
||||
def close(self):
|
||||
self.send_sock.close()
|
||||
self.recv_sock.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
perf_test = UDPPerformanceTest()
|
||||
try:
|
||||
# 1. 1553B时延测试(发送1000次)
|
||||
perf_test.test_1553b_latency(test_times=1000)
|
||||
time.sleep(3)
|
||||
|
||||
# 2. 10机并发测试(每机发送500次)
|
||||
perf_test.test_10_device_concurrent(test_times=500)
|
||||
time.sleep(3)
|
||||
|
||||
# 3. CAN/1553B消息顺序测试
|
||||
perf_test.test_can_b1553b_order()
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户终止性能测试")
|
||||
finally:
|
||||
perf_test.close()
|
||||
410
tests/udp_auto_test.py
Normal file
410
tests/udp_auto_test.py
Normal file
@@ -0,0 +1,410 @@
|
||||
"""
|
||||
虚拟仿真平台中间层软件 - 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()
|
||||
162
tests/udp_test_connect.py
Normal file
162
tests/udp_test_connect.py
Normal file
@@ -0,0 +1,162 @@
|
||||
'''
|
||||
功能对等客户端:模拟故障注入软件,发送/接收UDP数据
|
||||
author:Tan Mingyan
|
||||
更新日期:2025/2/26
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
2. 功能自测步骤
|
||||
步骤 1:启动工程主程序
|
||||
运行main.py,查看日志确认:
|
||||
外设配置成功加载(10 个外设);
|
||||
UDP 服务初始化完成,所有端口正常监听;
|
||||
无套接字绑定、配置解析错误。
|
||||
步骤 2:运行 UDP 测试客户端
|
||||
运行test/udp_test_client.py,执行单端口发送和多端口并行发送,验证以下核心功能:
|
||||
发送成功:客户端打印📤 发送数据成功,无端口连接失败;
|
||||
中间层接收成功:查看工程logs/run_*.log,打印对应端口的数据接收日志,无 16 进制解析错误;
|
||||
端口隔离性:多端口并行发送时,各端口数据独立接收,无串包、丢包(日志中各外设数据互不干扰);
|
||||
校验和检测:若发送数据包含正确校验位,日志打印校验和检测通过;若修改校验位为错误值,日志打印校验和检测失败并记录到logs/error_*.log;
|
||||
回传数据接收:若在工程中调用udp_server.send_to_fault()(如协议解析后回传),客户端能打印📥 接收中间层回传数据,数据格式正确。
|
||||
3. 常见问题排查
|
||||
表格
|
||||
问题现象 排查方向
|
||||
客户端提示发送数据失败 1. 中间层主程序是否未启动;2. 目标端口是否在 CSV 配置中;3. 端口是否被占用
|
||||
中间层未接收数据 1. 客户端发送的 IP 是否为127.0.0.1;2. 16 进制流格式是否正确(无乱码);3. 查看logs/error_*.log是否有数据源过滤(非故障注入 IP)
|
||||
校验和检测失败 1. 发送数据的校验位是否与error_check.py的算法匹配;2. 数据是否包含校验位(长度是否足够)
|
||||
多端口串包 1. CSV 配置中是否有重复端口;2. 工程udp_handler.py的_get_dev_by_port是否正确映射外设
|
||||
'''
|
||||
import socket
|
||||
import binascii
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
|
||||
# 设置标准输出和标准错误的编码为UTF-8
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
from pathlib import Path
|
||||
# 获取当前文件的目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 获取上级目录(项目根目录)
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
# 将项目根目录添加到系统路径
|
||||
sys.path.append(parent_dir)
|
||||
#sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
from midware.config.base_config import UDP_CONFIG
|
||||
|
||||
class UDPTestClient:
|
||||
"""UDP测试客户端:模拟故障注入软件,发送/接收UDP数据"""
|
||||
def __init__(self):
|
||||
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||
self.fault_port = UDP_CONFIG["FAULT_INJECT_PORT"] # 故障注入软件接收端口(客户端监听)
|
||||
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
||||
# 发送套接字(向中间层外设端口发包)
|
||||
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((self.local_ip, self.fault_port))
|
||||
self.recv_sock.setblocking(False)
|
||||
# 接收线程标识
|
||||
self.recv_running = True
|
||||
# 启动接收线程(后台接收,不阻塞发包)
|
||||
self.recv_thread = threading.Thread(target=self._recv_loop, daemon=True)
|
||||
self.recv_thread.start()
|
||||
print(f"UDP测试客户端初始化完成 | 监听回传数据:{self.local_ip}:{self.fault_port}")
|
||||
|
||||
def _recv_loop(self):
|
||||
"""后台接收线程:接收中间层回传的16进制流数据"""
|
||||
while self.recv_running:
|
||||
try:
|
||||
data, addr = self.recv_sock.recvfrom(self.buffer_size)
|
||||
hex_data = data.decode("utf-8").strip()
|
||||
raw_data = binascii.unhexlify(hex_data) if hex_data else b""
|
||||
print(f"\n📥 接收中间层回传数据 | 来源:{addr} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||
except BlockingIOError:
|
||||
time.sleep(0.001)
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"\n❌ 接收数据异常:{str(e)}")
|
||||
continue
|
||||
|
||||
def send_hex_data(self, dev_port, raw_data):
|
||||
"""
|
||||
向中间层指定外设端口发送16进制流数据(模拟故障注入软件发包)
|
||||
:param dev_port: 外设UDP端口(中间层监听端口)
|
||||
:param raw_data: 原始二进制数据,自动转为16进制流
|
||||
"""
|
||||
try:
|
||||
# 原始数据转16进制流(符合工程数据格式要求)
|
||||
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (self.local_ip, dev_port))
|
||||
print(f"\n📤 发送数据成功 | 目标端口:{dev_port} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"\n❌ 发送数据失败 | 目标端口:{dev_port} | 异常:{str(e)}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""关闭套接字,释放资源"""
|
||||
self.recv_running = False
|
||||
self.send_sock.close()
|
||||
self.recv_sock.close()
|
||||
print("\n✅ UDP测试客户端已关闭")
|
||||
|
||||
# 多端口并行发送测试(线程版)
|
||||
def multi_port_send(client, port_data_list):
|
||||
"""
|
||||
多端口并行发送数据,验证端口隔离性
|
||||
:param port_data_list: 列表,元素为(端口, 原始数据)
|
||||
"""
|
||||
def send_task(port, data):
|
||||
client.send_hex_data(port, data)
|
||||
# 模拟不同单机的发包间隔
|
||||
time.sleep(0.001)
|
||||
|
||||
threads = []
|
||||
for port, data in port_data_list:
|
||||
t = threading.Thread(target=send_task, args=(port, data), daemon=True)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
print("\n===== 多端口并行发送任务完成 =====")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 初始化测试客户端
|
||||
client = UDPTestClient()
|
||||
# 测试用原始数据(可自定义,建议包含校验位,适配error_check.py的校验逻辑)
|
||||
test_data1 = b"UART_TEST_DATA_001" + b'\x9c\x47' # UART数据+CRC16校验位
|
||||
test_data2 = b"CAN_TEST_DATA_001" + b'\x1a\x2b' # CAN数据+CRC16校验位
|
||||
test_data3 = b"1553B_TEST_DATA_001" + b'\x0f\x3d' # 1553B数据+CRC16校验位
|
||||
test_data4 = b"AD_TEST_DATA_001" + b'\x5f' # AD数据+累加和校验位
|
||||
test_data5 = b"OC_TEST_DATA_001" + b'\x6a' # OC数据+累加和校验位
|
||||
|
||||
try:
|
||||
# 1. 单端口发送测试(例:向Uart0(8880)发送数据)
|
||||
print("===== 单端口发送测试 =====")
|
||||
# client.send_hex_data(8880, test_data1)
|
||||
client.send_hex_data(4000, test_data1)
|
||||
time.sleep(1)
|
||||
|
||||
# 2. 多端口并行发送测试(验证端口互不干扰)
|
||||
print("\n===== 多端口并行发送测试 =====")
|
||||
port_data = [
|
||||
(4000, test_data1), (4001, test_data2), (4002, test_data3),
|
||||
(4003, test_data4), (4004, test_data1)
|
||||
]
|
||||
multi_port_send(client, port_data)
|
||||
time.sleep(3)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n用户终止测试")
|
||||
finally:
|
||||
# 关闭客户端
|
||||
client.close()
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user