• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (C) 2025 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17运行环境: python 3.10+
18测试方法:
19方法1、使用pytest框架执行
20请见测试用例根目录test\scripts下readme.md中的执行稳定性用例小节
21方法2、直接执行
22进入test\scripts目录,执行python .\testModule\stability_utils.py
23
24测试范围
25hdc shell
26hdc file send/recv
27hdc fport 文件传输
28多session场景测试(hdc tmode + hdc tconn)
29
30注意:
311、当前仅支持一台设备的压力测试
322、如果异常退出,请执行 hdc -t [usb_connect_key] tmode port close,关闭网络接口通道
33执行 hdc list targets查看是否还有网络连接,如果有请执行hdc tconn 127.0.0.1:port -remove断开
34执行hdc fport ls查看是否还有端口转发的规则,如果有请执行hdc fport rm tcp:xxxxx tcp:9999删除转发
35"""
36
37import subprocess
38import os
39import logging
40import logging.config
41import time
42from datetime import datetime
43from enum import Enum
44import threading
45import uuid
46import hashlib
47import queue
48from utils import server_loop
49from utils import client_get_file
50from utils import get_local_md5
51
52STABILITY_TEST_VERSION = "v1.0.0"
53
54TIME_REPORT_FORMAT = '%Y-%m-%d %H:%M:%S'
55TIME_FILE_FORMAT = '%Y%m%d_%H%M%S'
56TEST_RESULT = False
57
58WORK_DIR = os.getcwd()
59
60# 消息队列中使用到的key名称
61# 报告公共信息key名称
62REPORT_PUBLIC_INFO_NAME = "public_info"
63# 报告公共信息中的错误信息key名称
64TASK_ERROR_KEY_NAME = "task_error"
65
66# 资源文件相对路径
67RESOURCE_RELATIVE_PATH = "resource"
68# 报告文件相对路径
69REPORTS_RELATIVE_PATH = "reports"
70# 缓存查询到的设备sn
71DEVICE_LIST = []
72# 记录当前启动的session类信息
73SESSION_LIST = []
74# 释放资源命令列表
75RELEASE_TCONN_CMD_LIST = []
76RELEASE_FPORT_CMD_LIST = []
77
78RESOURCE_PATH = os.path.join(WORK_DIR, RESOURCE_RELATIVE_PATH)
79
80EXIT_EVENT = threading.Event()
81
82TEST_THREAD_MSG_QUEUE = queue.Queue()
83
84
85def get_time_str(fmt=TIME_REPORT_FORMAT, need_ms=False):
86    now_time = datetime.now()
87    if need_ms is False:
88        return now_time.strftime(fmt)
89    else:
90        return f"{now_time.strftime(fmt)}.{now_time.microsecond // 1000:03d}"
91
92
93TEST_FILE_TIME_STR = get_time_str(TIME_FILE_FORMAT)
94LOG_FILE_NAME = os.path.join(REPORTS_RELATIVE_PATH, f"hdc_stability_test_{TEST_FILE_TIME_STR}.log")
95REPORT_FILE_NAME = os.path.join(REPORTS_RELATIVE_PATH, f"hdc_stability_test_report_{TEST_FILE_TIME_STR}.html")
96logger = None
97
98# 配置日志记录
99logging_config_info = {
100    'version': 1,
101    'formatters': {
102        'format1': {
103            'format': '[%(asctime)s %(name)s %(funcName)s %(lineno)d %(levelname)s][%(process)d][%(thread)d]'
104                      '[%(threadName)s]%(message)s'
105        },
106    },
107    'handlers': {
108        'console_handle': {
109            'level': 'DEBUG',
110            'formatter': 'format1',
111            'class': 'logging.StreamHandler',
112            'stream': 'ext://sys.stdout',
113        },
114        'file_handle': {
115            'level': 'DEBUG',
116            'formatter': 'format1',
117            'class': 'logging.FileHandler',
118            'filename': LOG_FILE_NAME,
119        },
120    },
121    'loggers': {
122        '': {
123            'handlers': ['console_handle', 'file_handle'],
124            'level': 'DEBUG',
125        },
126    },
127}
128
129
130class TestType(Enum):
131    SHELL = 1
132    FILE_SEND = 2
133    FILE_RECV = 3
134    FPORT_TRANS_FILE = 4
135
136
137# 当前测试任务并行运行的数量
138TASK_COUNT_DEFAULT = 5
139# 循环测试次数
140LOOP_COUNT_DEFAULT = 20
141# 每个循环结束的等待时间,单位秒
142LOOP_DELAY_S_DEFAULT = 0.01
143
144# 进行多session测试时,启动多个端口转发到设备侧的tmode监听的端口,电脑端开启的端口列表
145# 参考端口配置:12345, 12346, 12347, 12348, 12349,需要几个session,可以复制几个放入下面的MUTIL_SESSION_TEST_PC_PORT_LIST中
146MUTIL_SESSION_TEST_PC_PORT_LIST = []
147# 多session测试, 通过tmode命令,设备端切换tcp模式,监听的端口号
148DEVICE_LISTEN_PORT = 9999
149
150# usb session 测试项配置
151"""
152配置项包括如下:
153    {
154        "test_type": TestType.SHELL,
155        "loop_count": LOOP_COUNT_DEFAULT,
156        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
157        "task_count": TASK_COUNT_DEFAULT,
158        "cmd": "shell ls",
159        "expected_results": "data",
160    },
161    {
162        "test_type": TestType.FILE_SEND,
163        "loop_count": LOOP_COUNT_DEFAULT,
164        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
165        "task_count": TASK_COUNT_DEFAULT,
166        "cmd": "file send",
167        "local": "medium",
168        "remote": "/data/medium",
169        "expected_results": "FileTransfer finish",
170    },
171    {
172        "test_type": TestType.FILE_RECV,
173        "loop_count": LOOP_COUNT_DEFAULT,
174        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
175        "task_count": TASK_COUNT_DEFAULT,
176        "cmd": "file recv",
177        "original_file": "medium",
178        "local": "medium",
179        "remote": "/data/medium",
180        "expected_results": "FileTransfer finish",
181    },
182    {
183        "test_type": TestType.FPORT_TRANS_FILE,
184        "port_info": [
185            {
186                "client_connect_port": 22345,
187                "daemon_transmit_port": 11081,
188                "server_listen_port": 18000,
189            },
190            {
191                "client_connect_port": 22346,
192                "daemon_transmit_port": 11082,
193                "server_listen_port": 18001,
194            },
195        ],
196        "loop_count": LOOP_COUNT_DEFAULT,
197        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
198        "original_file": "medium",
199    },
200"""
201USB_SESSION_TEST_CONFIG = [
202    {
203        "test_type": TestType.SHELL,
204        "loop_count": LOOP_COUNT_DEFAULT,
205        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
206        "task_count": TASK_COUNT_DEFAULT,
207        "cmd": "shell ls",
208        "expected_results": "data",
209    },
210    {
211        "test_type": TestType.FILE_SEND,
212        "loop_count": LOOP_COUNT_DEFAULT,
213        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
214        "task_count": TASK_COUNT_DEFAULT,
215        "cmd": "file send",
216        "local": "medium",
217        "remote": "/data/medium",
218        "expected_results": "FileTransfer finish",
219    },
220    {
221        "test_type": TestType.FILE_RECV,
222        "loop_count": LOOP_COUNT_DEFAULT,
223        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
224        "task_count": TASK_COUNT_DEFAULT,
225        "cmd": "file recv",
226        "original_file": "medium",
227        "local": "medium",
228        "remote": "/data/medium",
229        "expected_results": "FileTransfer finish",
230    },
231]
232
233# tcp session 测试项配置
234"""
235配置项包括如下:
236    {
237        "test_type": TestType.SHELL,
238        "loop_count": LOOP_COUNT_DEFAULT,
239        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
240        "task_count": TASK_COUNT_DEFAULT,
241        "cmd": "shell ls",
242        "expected_results": "data",
243    },
244    {
245        "test_type": TestType.FILE_SEND,
246        "loop_count": LOOP_COUNT_DEFAULT,
247        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
248        "task_count": TASK_COUNT_DEFAULT,
249        "cmd": "file send",
250        "local": "medium",
251        "remote": "/data/medium",
252        "expected_results": "FileTransfer finish",
253    },
254    {
255        "test_type": TestType.FILE_RECV,
256        "loop_count": LOOP_COUNT_DEFAULT,
257        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
258        "task_count": TASK_COUNT_DEFAULT,
259        "cmd": "file recv",
260        "original_file": "medium",
261        "local": "medium",
262        "remote": "/data/medium",
263        "expected_results": "FileTransfer finish",
264    },
265"""
266TCP_SESSION_TEST_CONFIG = [
267    {
268        "test_type": TestType.SHELL,
269        "loop_count": LOOP_COUNT_DEFAULT,
270        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
271        "task_count": TASK_COUNT_DEFAULT,
272        "cmd": "shell ls",
273        "expected_results": "data",
274    },
275    {
276        "test_type": TestType.FILE_SEND,
277        "loop_count": LOOP_COUNT_DEFAULT,
278        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
279        "task_count": TASK_COUNT_DEFAULT,
280        "cmd": "file send",
281        "local": "medium",
282        "remote": "/data/medium",
283        "expected_results": "FileTransfer finish",
284    },
285    {
286        "test_type": TestType.FILE_RECV,
287        "loop_count": LOOP_COUNT_DEFAULT,
288        "loop_delay_s": LOOP_DELAY_S_DEFAULT,
289        "task_count": TASK_COUNT_DEFAULT,
290        "cmd": "file recv",
291        "original_file": "medium",
292        "local": "medium",
293        "remote": "/data/medium",
294        "expected_results": "FileTransfer finish",
295    },
296]
297
298HTML_HEAD = """
299<head>
300<meta charset="UTF-8">
301<title>HDC稳定性测试报告</title>
302<style>
303    body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
304    table { border-collapse: collapse; width: 100%; }
305    th { background-color: #f0f0f0; }
306    th, td { border: 1px solid #ddd; padding: 6px; text-align: left; }
307    tr:nth-child(even) { background-color: #f9f9f9; }
308    .report {
309        max-width: 1200px;
310        margin: 0 auto;
311        background-color: #fafafa;
312        padding: 20px;
313        border-radius: 5px;
314        box-shadow: 0 0 10px rgba(0, 0, 0, 0.09);
315        color: #333;
316    }
317    .summary {
318        margin-bottom: 10px;
319        padding: 20px;
320        background-color: #fff;
321        border-radius: 5px;
322        box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05);
323    }
324    .summary-header { margin-bottom: 10px; padding-bottom: 5px; }
325    .summary-row {
326        display: flex;
327        justify-content: space-between;
328        margin-bottom: 2px;
329        flex-wrap: wrap;
330    }
331    .summary-item {
332        flex: 1;
333        min-width: 300px;
334        margin: 4px;
335        padding: 7px;
336        background-color: #f2f2f2;
337        border-radius: 4px;
338    }
339    .summary-item-key {
340        font-weight: bold;
341        margin-bottom: 3px;
342        display: block;
343    }
344    .summary-item-value { color: #444; }
345    .detail {
346        padding: 20px;
347        background-color: #fff;
348        border-radius: 5px;
349        box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05);
350    }
351    .detail-header {
352        margin-bottom: 10px;
353        border-bottom: 1px solid #eee;
354        padding-bottom: 10px;
355    }
356    .pass { color: green; }
357    .fail { color: red; }
358</style>
359</head>
360"""
361
362
363def init_logger():
364    logging.config.dictConfig(logging_config_info)
365
366
367def put_msg(queue_obj, info):
368    """
369    给队列中填入测试报告信息,用于汇总报告和生成结果
370    """
371    queue_obj.put(info)
372
373
374def run_cmd_block(command, timeout=600):
375    logger.info(f"cmd: {command}")
376    # 启动子进程
377    process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
378
379    # 用于存储子进程的输出
380    output = ""
381    error = ""
382
383    try:
384        # 读取子进程的输出
385        output, error = process.communicate(timeout=timeout)
386    except subprocess.TimeoutExpired:
387        logger.info(f"run cmd:{command} timeout")
388        process.terminate()
389        process.kill()
390        output, error = process.communicate(timeout=timeout)
391    return output, error
392
393
394def run_cmd_and_check_output(command, want_str, timeout=600):
395    """
396    阻塞的方式运行命令,然后判断标准输出的返回值中是否有预期的字符串
397    存在预期的字符串返回 True
398    不存在预期的字符串返回 False
399    """
400    output, error = run_cmd_block(command, timeout)
401    if want_str in output:
402        return True, (output, error)
403    else:
404        logger.error(f"can not get expect str:{want_str} cmd:{command} output:{output} error:{error}")
405        return False, (output, error)
406
407
408def process_shell_test(connect_key, config, thread_name):
409    """
410    config格式
411    {
412        "test_type": TestType.SHELL,
413        "loop_count": LOOP_COUNT_DEFAULT,
414        "loop_delay_s": 0.01,
415        "task_count": TASK_COUNT_DEFAULT,
416        "cmd": "shell ls",
417        "expected_results": "data",
418    }
419    """
420    logger.info(f"start process_shell_test thread {thread_name}")
421    test_count = config["loop_count"]
422    delay_s = config["loop_delay_s"]
423    cmd = f"hdc -t {connect_key} {config['cmd']}"
424    expected_results = config["expected_results"]
425    report = {"test_type": config["test_type"], "test_name": thread_name, "loop_count": test_count}
426    start_time = time.perf_counter()
427    for count in range(test_count):
428        logger.info(f"{thread_name} {count}")
429        is_ok, info = run_cmd_and_check_output(cmd, expected_results)
430        if is_ok:
431            logger.info(f"{thread_name} {count} success result:{info}")
432        else:
433            logger.error(f"{thread_name} loop_count:{count+1} run:{cmd} can not get expect result:{expected_results},"
434                         f"result:{info}")
435            error_time = get_time_str(need_ms=True)
436            msg = f"<span style='color: #0000ff;'>[{error_time}]</span> {thread_name} loop_count:{count+1} run:{cmd}" \
437                  f" can not get expect result:{expected_results}, result:{info}"
438            report["error_msg"] = f"[{error_time}] result:{info}"
439            TEST_THREAD_MSG_QUEUE.put({TASK_ERROR_KEY_NAME: {"name": thread_name, "error_msg": msg}})
440            EXIT_EVENT.set()
441            logger.info(f"{thread_name} {count} set exit event")
442            report["finished_test_count"] = count
443            break
444        if EXIT_EVENT.is_set():
445            logger.info(f"{thread_name} {count} exit event is set")
446            report["error_msg"] = "event is set, normal exit"
447            report["finished_test_count"] = count + 1
448            break
449        time.sleep(delay_s)
450    if "finished_test_count" not in report:
451        report["finished_test_count"] = test_count
452    if report["finished_test_count"] < test_count:
453        report["passed"] = False
454    else:
455        report["passed"] = True
456    elapsed_time = time.perf_counter() - start_time
457    report["cost_time"] = elapsed_time
458    TEST_THREAD_MSG_QUEUE.put({thread_name: report})
459    logger.info(f"stop process_shell_test thread {thread_name}")
460
461
462def process_file_send_test(connect_key, config, thread_name):
463    """
464    config格式
465    {
466        "test_type": TestType.FILE_SEND,
467        "loop_count": LOOP_COUNT_DEFAULT,
468        "loop_delay_s": 0.01,
469        "task_count": TASK_COUNT_DEFAULT,
470        "cmd": "file send",
471        "local": "medium",
472        "remote": "/data/medium",
473        "expected_results": "FileTransfer finish",
474    }
475    """
476    logger.info(f"start process_file_send_test thread {thread_name}")
477    test_count = config["loop_count"]
478    delay_s = config["loop_delay_s"]
479    expected_results = config["expected_results"]
480    local_file = os.path.join(RESOURCE_PATH, config['local'])
481    remote_file = f"{config['remote']}_{uuid.uuid4()}"
482    cmd = f"hdc -t {connect_key} {config['cmd']} {local_file} {remote_file}"
483    report = {"test_type": config["test_type"], "test_name": thread_name, "loop_count": test_count}
484    start_time = time.perf_counter()
485    for count in range(test_count):
486        logger.info(f"{thread_name} {count}")
487        is_ok, info = run_cmd_and_check_output(cmd, expected_results)
488        if is_ok:
489            logger.info(f"{thread_name} {count} success result:{info}")
490        else:
491            logger.error(f"{thread_name} loop_count:{count+1} run:{cmd} can not get expect result:{expected_results},"
492                         f"result:{info}")
493            error_time = get_time_str(need_ms=True)
494            msg = f"<span style='color: #0000ff;'>[{error_time}]</span> {thread_name} loop_count:{count+1} run:{cmd}" \
495                  f" can not get expect result:{expected_results}, result:{info}"
496            report["error_msg"] = f"[{error_time}] result:{info}"
497            TEST_THREAD_MSG_QUEUE.put({TASK_ERROR_KEY_NAME: {"name": thread_name, "error_msg": msg}})
498            EXIT_EVENT.set()
499            logger.info(f"{thread_name} {count} set exit event")
500            report["finished_test_count"] = count
501            break
502
503        run_cmd_block(f"hdc -t {connect_key} shell rm {remote_file}")
504        if EXIT_EVENT.is_set():
505            logger.info(f"{thread_name} {count} exit event is set")
506            report["error_msg"] = "event is set, normal exit"
507            report["finished_test_count"] = count + 1
508            break
509        time.sleep(delay_s)
510    if "finished_test_count" not in report:
511        report["finished_test_count"] = test_count
512    if report["finished_test_count"] < test_count:
513        report["passed"] = False
514    else:
515        report["passed"] = True
516    elapsed_time = time.perf_counter() - start_time
517    report["cost_time"] = elapsed_time
518    TEST_THREAD_MSG_QUEUE.put({thread_name: report})
519    logger.info(f"stop process_file_send_test thread {thread_name}")
520
521
522def process_file_recv_test(connect_key, config, thread_name):
523    """
524    config格式
525    {
526        "test_type": TestType.FILE_RECV,
527        "loop_count": LOOP_COUNT_DEFAULT,
528        "loop_delay_s": 0.01,
529        "task_count": TASK_COUNT_DEFAULT,
530        "cmd": "file recv",
531        "original_file": "medium",
532        "local": "medium",
533        "remote": "/data/medium",
534        "expected_results": "FileTransfer finish",
535    }
536    """
537    logger.info(f"start process_file_recv_test thread {thread_name}")
538    test_count = config["loop_count"]
539    delay_s = config["loop_delay_s"]
540    expected_results = config["expected_results"]
541    original_file = os.path.join(RESOURCE_PATH, config['original_file'])
542    name_id = uuid.uuid4()
543    local_file = os.path.join(RESOURCE_PATH, f"{config['local']}_{name_id}")
544    remote_file = f"{config['remote']}_{name_id}"
545    cmd = f"hdc -t {connect_key} {config['cmd']} {remote_file} {local_file}"
546    report = {"test_type": config["test_type"], "test_name": thread_name, "loop_count": test_count}
547    start_time = time.perf_counter()
548    run_cmd_block(f"hdc -t {connect_key} file send {original_file} {remote_file}")
549    for count in range(test_count):
550        logger.info(f"{thread_name} {count}")
551        is_ok, info = run_cmd_and_check_output(cmd, expected_results)
552        if is_ok:
553            logger.info(f"{thread_name} {count} success result:{info}")
554        else:
555            logger.error(f"{thread_name} loop_count:{count+1} run:{cmd} can not get expect result:{expected_results},"
556                         f"result:{info}")
557            error_time = get_time_str(need_ms=True)
558            msg = f"<span style='color: #0000ff;'>[{error_time}]</span> {thread_name} loop_count:{count+1} run:{cmd}" \
559                  f" can not get expect result:{expected_results}, result:{info}"
560            report["error_msg"] = f"[{error_time}] result:{info}"
561            TEST_THREAD_MSG_QUEUE.put({TASK_ERROR_KEY_NAME: {"name": thread_name, "error_msg": msg}})
562            EXIT_EVENT.set()
563            logger.info(f"{thread_name} {count} set exit event")
564            report["finished_test_count"] = count
565            break
566        os.remove(local_file)
567        logger.debug(f"{connect_key} remove {local_file} finished")
568        if EXIT_EVENT.is_set():
569            logger.info(f"{thread_name} {count} exit event is set")
570            report["error_msg"] = "event is set, normal exit"
571            report["finished_test_count"] = count + 1
572            break
573        time.sleep(delay_s)
574    if "finished_test_count" not in report:
575        report["finished_test_count"] = test_count
576    if report["finished_test_count"] < test_count:
577        report["passed"] = False
578    else:
579        report["passed"] = True
580    run_cmd_block(f"hdc -t {connect_key} shell rm {remote_file}")
581    elapsed_time = time.perf_counter() - start_time
582    report["cost_time"] = elapsed_time
583    TEST_THREAD_MSG_QUEUE.put({thread_name: report})
584    logger.info(f"stop process_file_recv_test thread {thread_name}")
585
586
587def create_fport_trans_test_env(info):
588    # 构建传输通道
589    thread_name = info.get("thread_name")
590    connect_key = info.get("connect_key")
591    fport_arg = info.get("fport_arg")
592    result, _ = run_cmd_block(f"hdc -t {connect_key} fport {fport_arg}")
593    logger.info(f"{thread_name} fport result:{result}")
594    rport_arg = info.get("rport_arg")
595    result, _ = run_cmd_block(f"hdc -t {connect_key} rport {rport_arg}")
596    logger.info(f"{thread_name} rport result:{result}")
597
598
599def do_fport_trans_file_test_once(client_connect_port, server_listen_port, file_name, file_save_name):
600    """
601    进行一次数据传输测试
602    """
603    logger.info(f"do_fport_trans_file_test_once start, file_save_name:{file_save_name}")
604    server_thread = threading.Thread(target=server_loop, args=(server_listen_port,))
605    server_thread.start()
606
607    client_get_file(client_connect_port, file_name, file_save_name)
608    server_thread.join()
609
610    ori_file_md5 = get_local_md5(os.path.join(RESOURCE_PATH, file_name))
611    new_file = os.path.join(RESOURCE_PATH, file_save_name)
612    new_file_md5 = 0
613    if os.path.exists(new_file):
614        new_file_md5 = get_local_md5(new_file)
615        os.remove(new_file)
616    logger.info(f"ori_file_md5:{ori_file_md5}, new_file_md5:{new_file_md5}")
617
618    if not ori_file_md5 == new_file_md5:
619        logger.error(f"check file md5 failed, file_save_name:{file_save_name}, ori_file_md5:{ori_file_md5},"
620                     f"new_file_md5:{new_file_md5}")
621        return False
622    logger.info(f"do_fport_trans_file_test_once exit, file_save_name:{file_save_name}")
623    return True
624
625
626def process_fport_trans_file_test(connect_key, config, thread_name, task_index):
627    """
628    task_index对应当前测试的port_info,从0开始计数。
629    config格式
630    {
631        "test_type": TestType.FPORT_TRANS_FILE,
632        "port_info": [
633            {
634                "client_connect_port": 22345,
635                "daemon_transmit_port": 11081,
636                "server_listen_port": 18000,
637            },
638            {
639                "client_connect_port": 22346,
640                "daemon_transmit_port": 11082,
641                "server_listen_port": 18001,
642            },
643        ],
644
645        "loop_count": LOOP_COUNT_DEFAULT,
646        "loop_delay_s": 0.01,
647        "original_file": "medium",
648    }
649    """
650    logger.info(f"start process_fport_trans_file_test thread {thread_name}")
651    test_count = config["loop_count"]
652    delay_s = config["loop_delay_s"]
653    port_info = config["port_info"]
654    client_connect_port = port_info[task_index]["client_connect_port"]
655    daemon_transmit_port = port_info[task_index]["daemon_transmit_port"]
656    server_listen_port = port_info[task_index]["server_listen_port"]
657    fport_arg = f"tcp:{client_connect_port} tcp:{daemon_transmit_port}"
658    rport_arg = f"tcp:{daemon_transmit_port} tcp:{server_listen_port}"
659
660    report = {"test_type": config["test_type"], "test_name": thread_name, "loop_count": test_count}
661    start_time = time.perf_counter()
662
663    # 构建传输通道
664    create_fport_trans_test_env({"thread_name": thread_name, "connect_key": connect_key,
665        "fport_arg": fport_arg, "rport_arg": rport_arg})
666
667    # transmit file start
668    file_name = config["original_file"]
669    file_save_name = f"{file_name}_recv_fport_{uuid.uuid4()}"
670    for count in range(test_count):
671        logger.info(f"{thread_name} {count}")
672        if do_fport_trans_file_test_once(client_connect_port, server_listen_port, file_name, file_save_name) is False:
673            error_time = get_time_str(need_ms=True)
674            msg = f"<span style='color: #0000ff;'>[{error_time}]</span> {thread_name} loop_count:{count+1}" \
675                  f" check file md5 failed"
676            report["error_msg"] = f"[{error_time}] check file md5 failed"
677            TEST_THREAD_MSG_QUEUE.put({TASK_ERROR_KEY_NAME: {"name": thread_name, "error_msg": msg}})
678            EXIT_EVENT.set()
679            logger.info(f"{thread_name} {count} set exit event")
680            report["finished_test_count"] = count
681            break
682        if EXIT_EVENT.is_set():
683            logger.info(f"{thread_name} {count} exit event is set")
684            report["error_msg"] = "event is set, normal exit"
685            report["finished_test_count"] = count + 1
686            break
687        time.sleep(delay_s)
688
689    # 关闭fport通道
690    run_cmd_block(f"hdc -t {connect_key} fport rm {fport_arg}")
691    run_cmd_block(f"hdc -t {connect_key} fport rm {rport_arg}")
692    if "finished_test_count" not in report:
693        report["finished_test_count"] = test_count
694    if report["finished_test_count"] < test_count:
695        report["passed"] = False
696    else:
697        report["passed"] = True
698    elapsed_time = time.perf_counter() - start_time
699    report["cost_time"] = elapsed_time
700    TEST_THREAD_MSG_QUEUE.put({thread_name: report})
701    logger.info(f"stop process_fport_trans_file_test thread {thread_name}")
702
703
704def get_test_result(fail_num):
705    """
706    返回错误结果及错误结果显示类型信息
707    """
708    global TEST_RESULT
709    if fail_num > 0:
710        test_result = "失败"
711        TEST_RESULT = False
712        test_result_class = "fail"
713    else:
714        test_result = "通过"
715        TEST_RESULT = True
716        test_result_class = "pass"
717    return test_result, test_result_class
718
719
720def get_error_msg_html(public_info):
721    """
722    获取html格式的错误信息
723    """
724    error_msg_list = public_info.get(TASK_ERROR_KEY_NAME)
725    error_msg_html = ''
726    if error_msg_list is not None:
727        error_msg = '\n'.join(error_msg_list)
728        error_msg_html = f"""
729            <div class="summary-row">
730                <div class="summary-item">
731                    <span class="summary-item-key">错误信息</span>
732                    <span class="summary-item-value fail">{error_msg}</span>
733                </div>
734            </div>
735        """
736    return error_msg_html
737
738
739def gen_report_public_info(public_info):
740    """
741    生成html格式的报告公共信息部分
742    """
743    start_time = public_info.get('start_time')
744    stop_time = public_info.get('stop_time')
745    pass_num = public_info.get('pass_num')
746    fail_num = public_info.get('fail_num')
747    test_task_num = pass_num + fail_num
748    test_result, test_result_class = get_test_result(fail_num)
749
750    # 错误信息
751    error_msg_html = get_error_msg_html(public_info)
752
753    public_html_temp = f"""
754        <div class="summary-row">
755            <div class="summary-item">
756                <span class="summary-item-key">用例版本号</span>
757                <span class="summary-item-value">{STABILITY_TEST_VERSION}</span>
758            </div>
759            <div class="summary-item">
760                <span class="summary-item-key">开始时间</span>
761                <span class="summary-item-value">{start_time}</span>
762            </div>
763            <div class="summary-item">
764                <span class="summary-item-key">结束时间</span>
765                <span class="summary-item-value">{stop_time}</span>
766            </div>
767        </div>
768        <div class="summary-row">
769            <div class="summary-item">
770                <span class="summary-item-key">测试任务数</span>
771                <span class="summary-item-value">{test_task_num}</span>
772            </div>
773            <div class="summary-item">
774                <span class="summary-item-key">失败任务数</span>
775                <span class="summary-item-value fail">{fail_num}</span>
776            </div>
777            <div class="summary-item">
778                <span class="summary-item-key">成功任务数</span>
779                <span class="summary-item-value pass">{pass_num}</span>
780            </div>
781        </div>
782        <div class="summary-row">
783            <div class="summary-item">
784                <span class="summary-item-key">测试结果</span>
785                <span class="summary-item-value {test_result_class}">{test_result}</span>
786            </div>
787        </div>
788        {error_msg_html}
789    """
790    return public_html_temp
791
792
793def gen_report_detail_info(pass_list, fail_list):
794    """
795    生成html格式的报告详细信息部分
796    pass_list fail_list 格式:[("测试名称", {"key": value, "key": value, "key": value, "key": value ...}), ... ]
797    """
798    # 生成表格每一行记录
799    detail_rows_list = []
800    for one_test_key, one_test_value in fail_list + pass_list:
801        if one_test_value.get("passed") is True:
802            result_class = "pass"
803            result_text = "通过"
804        else:
805            result_class = "fail"
806            result_text = "失败"
807        finished_test_count = one_test_value.get("finished_test_count")
808        loop_count = one_test_value.get("loop_count")
809        completion_rate = 100.0 * finished_test_count / loop_count
810        cost_time = one_test_value.get("cost_time")
811        if finished_test_count > 0:
812            cost_time_per_test = f"{cost_time / finished_test_count:.3f}"
813        else:
814            cost_time_per_test = "NA"
815        one_row = f"""
816            <tr>
817                <td>{one_test_key}</td>
818                <td class="{result_class}">{result_text}</td>
819                <td>{completion_rate:.1f}</td>
820                <td>{finished_test_count}</td>
821                <td>{loop_count}</td>
822                <td>{cost_time_per_test}</td>
823                <td>{cost_time:.3f}</td>
824                <td>{one_test_value.get("error_msg", "NA")}</td>
825            </tr>
826        """
827        detail_rows_list.append(one_row)
828    detail_rows = ''.join(detail_rows_list)
829
830    # 合成表格
831    detail_table_temp = f"""
832        <table>
833            <tr>
834                <th>任务名称</th>
835                <th>测试结果</th>
836                <th>完成率</th>
837                <th>已完成次数</th>
838                <th>总次数</th>
839                <th>平均每轮耗时(秒)</th>
840                <th>总耗时(秒)</th>
841                <th>错误信息</th>
842            </tr>
843            {detail_rows}
844        </table>
845    """
846    return detail_table_temp
847
848
849def gen_report_info(public_info, detail_info):
850    """
851    生成html格式的报告
852    """
853    html_temp = f"""
854    <!DOCTYPE html>
855    <html lang="zh-CN">
856    {HTML_HEAD}
857    <body>
858        <div class="report">
859            <section class="summary">
860                <h1 style="text-align: center;">HDC稳定性测试报告</h1>
861                <h2 class="summary-header">概要</h2>
862                {public_info}
863            </section>
864            <section class="detail">
865                <h2 class="detail-header">详情</h2>
866                {detail_info}
867            </section>
868        </div>
869    </body>
870    </html>
871    """
872    return html_temp
873
874
875def save_report(report_info, save_file_name):
876    """
877    生成测试报告
878    """
879    # 字典中获取报告公共信息
880    public_info = report_info.get(REPORT_PUBLIC_INFO_NAME)
881    # 字典中获取详细测试项目的信息
882    detail_test_info = {key: value for key, value in report_info.items() if key != REPORT_PUBLIC_INFO_NAME}
883
884    # 分类测试结果
885    pass_list = []
886    fail_list = []
887    for one_test_key, one_test_value in detail_test_info.items():
888        if one_test_value.get("passed") is True:
889            pass_list.append((one_test_key, one_test_value))
890        else:
891            fail_list.append((one_test_key, one_test_value))
892
893    # 排序成功和失败的任务列表
894    pass_list = sorted(pass_list)
895    fail_list = sorted(fail_list)
896    public_info["pass_num"] = len(pass_list)
897    public_info["fail_num"] = len(fail_list)
898
899    # 生成报告公共信息
900    public_info_html = gen_report_public_info(public_info)
901
902    # 生成报告详细信息
903    detail_info_html = gen_report_detail_info(pass_list, fail_list)
904
905    # 合成报告
906    report_info_html = gen_report_info(public_info_html, detail_info_html)
907
908    # 写入文件
909    with open(save_file_name, "w", encoding="utf-8") as f:
910        f.write(report_info_html)
911
912
913def add_error_msg_to_public_info(err_msg, report_info):
914    """
915    将一条错误信息追加到public_info里面的task_error字段中
916    err_msg的结构为{"name": "", "error_msg": ""}
917    """
918    msg = err_msg.get("error_msg")
919    if REPORT_PUBLIC_INFO_NAME in report_info:
920        public_info = report_info.get(REPORT_PUBLIC_INFO_NAME)
921        if TASK_ERROR_KEY_NAME in public_info:
922            public_info[TASK_ERROR_KEY_NAME].append(msg)
923        else:
924            public_info[TASK_ERROR_KEY_NAME] = [msg, ]
925    else:
926        report_info[REPORT_PUBLIC_INFO_NAME] = {TASK_ERROR_KEY_NAME: [msg, ]}
927
928
929def add_to_report_info(one_info, report_info):
930    """
931    将一条信息添加到报告信息集合中,
932    不存在则新增,存在则添加或者覆盖以前的值
933    one_info格式为 {key: {key1: value ...}, key: {key1: value ...}...}
934    当前的key包括如下几类:
935    1、任务名称:[sn]_filerecv_0 或者 [ip:port]_filerecv_0
936    2、公共信息:REPORT_PUBLIC_INFO_NAME = "public_info"
937    3、测试任务报告的错误信息:TASK_ERROR_KEY_NAME = "task_error"
938    最终追加到public_info里面的task_error字段中
939    """
940    for report_section_add, section_dict_add in one_info.items():
941        if report_section_add == TASK_ERROR_KEY_NAME:
942            # 追加error msg到public_info里面的task_error字段中
943            add_error_msg_to_public_info(section_dict_add, report_info)
944            continue
945        if report_section_add in report_info:
946            # 报告数据中已经存在待添加的字段,则获取报告数据中的该段落,给段落中增加或者覆盖已有字段
947            report_section_dict = report_info.get(report_section_add)
948            report_section_dict.update(section_dict_add)
949        else:
950            report_info[report_section_add] = section_dict_add
951
952
953def process_msg(msg_queue, thread_name):
954    """
955    消息中心,用于接收所有测试线程的消息,保存到如下结构的字典report_info中,用于生成报告:
956    {
957    "public_info": {"key": value, "key": value, "key": value, "key": value ...}
958    "thread_name1": {"key": value, "key": value, "key": value, "key": value ...}
959    "thread_name2": {"key": value, "key": value, "key": value, "key": value ...}
960    }
961    public_info为报告开头的公共信息
962    thread_namex为各个测试项目的测试信息
963    通过msg_queue,传递过来的消息,格式为 {key: {key1: value ...}, key: {key1: value ...}...}的格式,
964    key表示上面report_info的key,不存在则新增,存在则添加或者覆盖以前的值
965    """
966    logger.info(f"start process_msg thread {thread_name}")
967    report_info = {}
968    while True:
969        one_info = msg_queue.get()
970        if one_info is None:  # 报告接收完毕,退出
971            logger.info(f"{thread_name} get None from queue")
972            break
973        add_to_report_info(one_info, report_info)
974    logger.info(f"start save report to {REPORT_FILE_NAME}, report len:{len(report_info)}")
975    save_report(report_info, REPORT_FILE_NAME)
976    logger.info(f"stop process_msg thread {thread_name}")
977
978
979class Session(object):
980    """
981    session class
982    一个session连接对应一个session class
983    包含了所有在当前连接下面的测试任务
984    """
985    # session类型,usb or tcp
986    session_type = ""
987    # 设备连接标识
988    connect_key = ""
989    test_config = []
990    thread_list = []
991
992    def __init__(self, connect_key):
993        self.connect_key = connect_key
994
995    def set_test_config(self, config):
996        self.test_config = config
997
998    def start_test(self):
999        if len(self.test_config) == 0:
1000            logger.error(f"start test test_config is empty, do not test, type:{self.session_type} {self.connect_key}")
1001            return True
1002        for one_config in self.test_config:
1003            if self.start_one_test(one_config) is False:
1004                logger.error(f"start one test failed, type:{self.session_type} {self.connect_key} config:{one_config}")
1005                return False
1006        return True
1007
1008    def start_one_test(self, config):
1009        if config["test_type"] == TestType.SHELL:
1010            if self.start_shell_test(config) is False:
1011                logger.error(f"start_shell_test failed, type:{self.session_type} {self.connect_key} config:{config}")
1012                return False
1013        if config["test_type"] == TestType.FILE_SEND:
1014            if self.start_file_send_test(config) is False:
1015                logger.error(f"start_file_send_test failed, type:{self.session_type} \
1016                    {self.connect_key} config:{config}")
1017                return False
1018        if config["test_type"] == TestType.FILE_RECV:
1019            if self.start_file_recv_test(config) is False:
1020                logger.error(f"start_file_recv_test failed, type:{self.session_type} \
1021                    {self.connect_key} config:{config}")
1022                return False
1023        if config["test_type"] == TestType.FPORT_TRANS_FILE:
1024            if self.start_fport_trans_file_test(config) is False:
1025                logger.error(f"start_fport_trans_file_test failed, type:{self.session_type} \
1026                    {self.connect_key} config:{config}")
1027                return False
1028        return True
1029
1030    def start_shell_test(self, config):
1031        task_count = config["task_count"]
1032        test_name = f"{self.connect_key}_shell"
1033        for task_index in range(task_count):
1034            thread = threading.Thread(target=process_shell_test, name=f"{test_name}_{task_index}",
1035                                      args=(self.connect_key, config, f"{test_name}_{task_index}"))
1036            thread.start()
1037            self.thread_list.append(thread)
1038
1039    def start_file_send_test(self, config):
1040        task_count = config["task_count"]
1041        test_name = f"{self.connect_key}_filesend"
1042        for task_index in range(task_count):
1043            thread = threading.Thread(target=process_file_send_test, name=f"{test_name}_{task_index}",
1044                                      args=(self.connect_key, config, f"{test_name}_{task_index}"))
1045            thread.start()
1046            self.thread_list.append(thread)
1047
1048    def start_file_recv_test(self, config):
1049        task_count = config["task_count"]
1050        test_name = f"{self.connect_key}_filerecv"
1051        for task_index in range(task_count):
1052            thread = threading.Thread(target=process_file_recv_test, name=f"{test_name}_{task_index}",
1053                                      args=(self.connect_key, config, f"{test_name}_{task_index}"))
1054            thread.start()
1055            self.thread_list.append(thread)
1056
1057    def start_fport_trans_file_test(self, config):
1058        task_count = len(config["port_info"])
1059        test_name = f"{self.connect_key}_fporttrans"
1060        for task_index in range(task_count):
1061            thread = threading.Thread(target=process_fport_trans_file_test, name=f"{test_name}_{task_index}",
1062                                      args=(self.connect_key, config, f"{test_name}_{task_index}", task_index))
1063            thread.start()
1064            self.thread_list.append(thread)
1065
1066
1067class UsbSession(Session):
1068    """
1069    usb连接对应的session类
1070    """
1071    session_type = "usb"
1072
1073
1074class TcpSession(Session):
1075    """
1076    tcp连接对应的session类
1077    """
1078    session_type = "tcp"
1079
1080    def start_tcp_connect(self):
1081        result, _ = run_cmd_block(f"hdc tconn {self.connect_key}")
1082        logger.info(f"start_tcp_connect {self.connect_key} result:{result}")
1083        RELEASE_TCONN_CMD_LIST.append(f"hdc tconn {self.connect_key} -remove")
1084        return True
1085
1086
1087def start_server():
1088    cmd = "hdc start"
1089    result, _ = run_cmd_block(cmd)
1090    return result
1091
1092
1093def get_dev_list():
1094    try:
1095        result, _ = run_cmd_block("hdc list targets")
1096        result = result.split()
1097    except (OSError, IndexError):
1098        result = ["failed to detect device"]
1099        return False, result
1100    targets = result
1101    if len(targets) == 0:
1102        logger.error(f"get device, devices list is empty")
1103        return False, []
1104    if "[Empty]" in targets[0]:
1105        logger.error(f"get device, no devices found, devices:{targets}")
1106        return False, targets
1107    return True, targets
1108
1109
1110def check_device_online(connect_key):
1111    """
1112    确认设备是否在线
1113    """
1114    result, devices = get_dev_list()
1115    if result is False:
1116        logger.error(f"get device failed, devices:{devices}")
1117        return False
1118    if connect_key in devices:
1119        return True
1120    return False
1121
1122
1123def init_test_env():
1124    """
1125    初始化测试环境
1126    1、使用tmode port和fport转发,构建多session tcp测试场景
1127    """
1128    logger.info(f"init env")
1129    start_server()
1130    result, devices = get_dev_list()
1131    if result is False:
1132        logger.error(f"get device failed, devices:{devices}")
1133        return False
1134    device_sn = devices[0]
1135    if len(devices) > 1:
1136        # 存在多个设备连接,获取不是ip:port的设备作为待测试的设备
1137        logger.info(f"Multiple devices are connected, devices:{devices}")
1138        for dev in devices:
1139            if ':' not in dev:
1140                device_sn = dev
1141                # 关闭设备侧监听端口
1142                result, _ = run_cmd_block(f"hdc -t {device_sn} tmode port close")
1143                logger.info(f"close tmode port finished")
1144                time.sleep(10)
1145
1146    logger.info(f"get device:{device_sn}")
1147
1148    # 开启设备侧监听端口
1149    result, _ = run_cmd_block(f"hdc -t {device_sn} tmode port {DEVICE_LISTEN_PORT}")
1150    logger.info(f"run tmode port {DEVICE_LISTEN_PORT}, result:{result}")
1151    time.sleep(10)
1152    logger.info(f"start check device:{device_sn}")
1153    if check_device_online(device_sn) is False:
1154        logger.error(f"device {device_sn} in not online")
1155        return False
1156    logger.info(f"init_test_env finished")
1157    return True
1158
1159
1160def start_usb_session_test(connect_key):
1161    """
1162    开始一个usb session的测试
1163    """
1164    logger.info(f"start_usb_session_test connect_key:{connect_key}")
1165    session = UsbSession(connect_key)
1166    session.set_test_config(USB_SESSION_TEST_CONFIG)
1167    if session.start_test() is False:
1168        logger.error(f"session.start_test failed, connect_key:{connect_key}")
1169        return False
1170    SESSION_LIST.append(session)
1171    logger.info(f"start_usb_session_test connect_key:{connect_key} finished")
1172    return True
1173
1174
1175def start_local_tcp_session_test(connect_key, port_list):
1176    """
1177    1、遍历传入的端口号,创建fport端口转发到设备端的DEVICE_LISTEN_PORT,
1178    2、使用tconn连接后进行测试
1179    """
1180    logger.info(f"start_local_tcp_session_test port_list:{port_list}")
1181    if len(TCP_SESSION_TEST_CONFIG) == 0 or len(port_list) == 0:
1182        logger.info(f"port_list:{port_list} TCP_SESSION_TEST_CONFIG:{TCP_SESSION_TEST_CONFIG}")
1183        logger.info(f"port_list or TCP_SESSION_TEST_CONFIG is empty, no need do local tcp session test, exit")
1184        return True
1185    for port in port_list:
1186        # 构建fport通道
1187        logger.info(f"create fport local:{port} device:{DEVICE_LISTEN_PORT}")
1188        result, _ = run_cmd_block(f"hdc -t {connect_key} fport tcp:{port} tcp:{DEVICE_LISTEN_PORT}")
1189        logger.info(f"create fport local:{port} device:{DEVICE_LISTEN_PORT} result:{result}")
1190        RELEASE_FPORT_CMD_LIST.append(f"hdc -t {connect_key} fport rm tcp:{port} tcp:{DEVICE_LISTEN_PORT}")
1191        tcp_key = f"127.0.0.1:{port}"
1192        logger.info(f"start TcpSession connect_key:{tcp_key}")
1193        session = TcpSession(tcp_key)
1194        session.set_test_config(TCP_SESSION_TEST_CONFIG)
1195        session.start_tcp_connect()
1196        if session.start_test() is False:
1197            logger.error(f"session.start_test failed, connect_key:{tcp_key}")
1198            return False
1199        SESSION_LIST.append(session)
1200        logger.info(f"start tcp test connect_key:{tcp_key} finished")
1201
1202    logger.info(f"start_local_tcp_session_test finished")
1203    return True
1204
1205
1206def start_test(msg_queue):
1207    """
1208    启动测试
1209    1、启动usb session场景的测试
1210    2、启动通过 tmode+fport模拟的tcp session场景的测试
1211    """
1212    logger.info(f"start test")
1213    result, devices = get_dev_list()
1214    if result is False:
1215        logger.error(f"get device failed, devices:{devices}")
1216        return False
1217    device_sn = devices[0]
1218    DEVICE_LIST.append(device_sn)
1219    if start_usb_session_test(device_sn) is False:
1220        logger.error(f"start_usb_session_test failed, devices:{devices}")
1221        return False
1222    if start_local_tcp_session_test(device_sn, MUTIL_SESSION_TEST_PC_PORT_LIST) is False:
1223        logger.error(f"start_tcp_session_test failed, devices:{devices}")
1224        return False
1225    return True
1226
1227
1228def release_resource():
1229    """
1230    释放相关资源
1231    1、断开tconn连接
1232    2、fport转发
1233    3、tmode port
1234    """
1235    logger.info(f"enter release_resource")
1236    for cmd in RELEASE_TCONN_CMD_LIST:
1237        result, _ = run_cmd_block(cmd)
1238        logger.info(f"release tconn cmd:{cmd} result:{result}")
1239    for cmd in RELEASE_FPORT_CMD_LIST:
1240        result, _ = run_cmd_block(cmd)
1241        logger.info(f"release fport cmd:{cmd} result:{result}")
1242    result, _ = run_cmd_block(f"hdc -t {DEVICE_LIST[0]} tmode port close")
1243    logger.info(f"tmode port close, device:{DEVICE_LIST[0]} result:{result}")
1244
1245
1246def run_stability_test():
1247    global logger
1248    logger = logging.getLogger(__name__)
1249    logger.info(f"main resource_path:{RESOURCE_PATH}")
1250    start_time = get_time_str()
1251    if init_test_env() is False:
1252        logger.error(f"init_test_env failed")
1253        return False
1254
1255    # 启动消息收集进程
1256    msg_thread = threading.Thread(target=process_msg, name="msg_process", args=(TEST_THREAD_MSG_QUEUE, "msg_process"))
1257    msg_thread.start()
1258
1259    put_msg(TEST_THREAD_MSG_QUEUE, {"public_info": {"start_time": start_time}})
1260    logger.info(f"start run test thread")
1261    if start_test(TEST_THREAD_MSG_QUEUE) is False:
1262        logger.error(f"start_test failed")
1263        stop_time = get_time_str()
1264        put_msg(TEST_THREAD_MSG_QUEUE, {"public_info": {"stop_time": stop_time}})
1265        TEST_THREAD_MSG_QUEUE.put(None)
1266        msg_thread.join()
1267        return False
1268
1269    # wait all thread exit
1270    logger.info(f"wait all test thread exit")
1271    for session in SESSION_LIST:
1272        for thread in session.thread_list:
1273            thread.join()
1274
1275    stop_time = get_time_str()
1276    put_msg(TEST_THREAD_MSG_QUEUE, {"public_info": {"stop_time": stop_time}})
1277
1278    release_resource()
1279
1280    # 传递 None 参数,报告进程收到后退出
1281    logger.info(f"main put None to TEST_THREAD_MSG_QUEUE")
1282    TEST_THREAD_MSG_QUEUE.put(None)
1283    msg_thread.join()
1284
1285    logger.info(f"exit main, test result:{TEST_RESULT}")
1286    return TEST_RESULT
1287
1288
1289if __name__ == "__main__":
1290    init_logger()
1291    run_stability_test()
1292