Prhub

#7405 [CI] Add pytest failure log collection and persistence

PaddlePaddle/FastDeploy · 作者 EmmonsCurse · 合并时间 2026-04-16 22:56

分析状态 已生成
文件变更 3提交数 4 · 评论 12
代码增减 +70 / -22
CI test infra

执行摘要

为 CI 添加 pytest 失败日志收集与持久化功能,提升调试效率。

根据PR body描述,动机是“增强CI调试能力,通过收集详细的pytest失败日志,减少不稳定或失败用例的排查时间”。这旨在解决CI环境中测试失败时调试困难的问题,提升开发和维护效率。

该PR值得快速浏览,重点关注pytest_runtest_makereport钩子的实现方式,以及文件名清洗和导入结构调整的设计决策。对于CI基础设施维护者,可借鉴其日志收集机制以优化其他项目的测试调试流程。

讨论亮点
  1. 文件名安全性问题:PaddlePaddle-bot指出case_name未做安全清洗,参数化测试中含/等字符可能导致日志写入失败,建议添加清理逻辑。作者在后续提交中通过正则表达式修复了此问题。
  2. 导入风险疑问:PaddlePaddle-bot询问将e2e.utils.serving_utils提升为顶层导入是否会导致非e2e测试的collection失败,但未得到明确回复,此疑虑未解决。
  3. 脚本选项位置建议:PaddlePaddle-bot建议将--exclude选项放在位置参数前以避免POSIX兼容性问题,但PR中未采纳此建议。
  4. 临时测试修改:Review中发现了多个测试文件中的断言被反转为!=,这是用于验证日志收集功能的临时修改,作者在后续提交中移除了这些调试代码。

实现拆解

  1. 新增pytest钩子实现日志收集:在tests/conftest.py中新增pytest_runtest_makereport钩子,捕获测试失败时的错误信息,并写入FD_LOG_DIR目录下的日志文件,文件名基于测试用例名生成。
  2. 调整conftest.py导入和结构:将e2e.utils.serving_utils导入移至文件顶部,并添加日志目录创建逻辑到pytest_configure中,同时重构代码注释以提升可读性。
  3. 禁用不稳定测试文件:将tests/e2e/4cards_cases/test_Qwen3_30b_tp4.py重命名为_test_Qwen3_30b_tp4.py,以禁用该不稳定测试。
  4. 更新CI脚本排除干扰:修改scripts/coverage_run.sh,在grep命令中添加--exclude="pytest_*_error.log"选项,避免日志收集产生的错误日志干扰CI输出。
文件 模块 状态 重要度
tests/conftest.py 测试框架 modified 6.43
tests/e2e/4cards_cases/_test_Qwen3_30b_tp4.py 端到端测试 renamed 3.11
scripts/coverage_run.sh 脚本工具 modified 2.14
tests/conftest.py test-coverage

核心变更文件,新增 pytest 钩子实现日志收集,并调整导入和配置逻辑。

import os
import re
import pytest@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
    捕获失败测试用例并将错误日志保存到FD_LOG_DIR。
    仅记录测试执行阶段的失败,不包含setup/teardown阶段。
    """
    outcome = yield
    report = outcome.get_result()
​
    if report.when == "call" and report.failed:
        # 从item.nodeid提取测试用例名,并清洗非法字符以确保文件名安全
        raw_name = item.nodeid.split("::", 1)[-1]
        case_name = re.sub(r'[\\/:*?"<>|]', '_', raw_name) # 替换特殊字符为下划线
​
        log_dir = os.environ.get("FD_LOG_DIR", "log")
        log_file = os.path.join(log_dir, f"pytest_{case_name}_error.log")
​
        # 将错误信息写入日志文件
        with open(log_file, "w", encoding="utf-8") as f:
            f.write(f"Test failed: {item.nodeid}\n")
            if call.excinfo is not None:
                f.write(f"Exception: {call.excinfo}\n")
            if report.longrepr is not None:
                f.write(f"Traceback: {report.longrepr}\n")

关键符号

pytest_runtest_makereport pytest_configure pytest_collection_modifyitems

评论区精华

文件名安全性清洗 正确性

PaddlePaddle-bot 指出 case_name 未做安全清洗,参数化测试中含特殊字符可能导致日志写入失败。

结论:作者在后续提交中添加了正则表达式清洗逻辑,替换非法字符为下划线。 · 已解决

顶层导入风险 设计

PaddlePaddle-bot 询问将 e2e.utils.serving_utils 提升为顶层导入是否会导致非 e2e 测试 collection 失败。

结论:未得到明确回复,疑虑未解决,可能依赖环境配置。 · unresolved

脚本选项位置 style

PaddlePaddle-bot 建议将 --exclude 选项放在位置参数前以避免 POSIX 兼容性问题。

结论:PR 中未采纳此建议,选项仍放在位置参数后。 · unresolved

风险与影响

  1. 导入依赖风险conftest.py中顶层导入e2e.utils.serving_utils,若该模块在非e2e测试环境中不可用,可能导致所有测试的collection阶段失败。
  2. 日志文件路径安全:尽管已添加文件名清洗,但case_name清洗逻辑可能未覆盖所有边缘情况,如过长文件名或特殊编码字符,仍可能引发文件操作异常。
  3. CI脚本兼容性coverage_run.sh--exclude选项放在位置参数后,在严格POSIX环境下可能失效,导致错误日志未被排除,干扰CI输出。
  4. 测试覆盖影响:禁用test_Qwen3_30b_tp4.py可能降低对该模型特定配置的测试覆盖,需确保其他测试能补偿。
  1. 对用户影响:无直接影响,主要面向开发者和CI维护者,提升测试失败时的调试效率。
  2. 对系统影响:新增日志文件可能增加磁盘使用量,但通过环境变量FD_LOG_DIR可控制目录,影响有限。
  3. 对团队影响:简化CI故障排查流程,减少手动日志收集时间,提升开发迭代速度。
导入依赖风险 文件路径安全 脚本兼容性

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

执行摘要

  • 一句话:为CI添加pytest失败日志收集与持久化功能,提升调试效率。
  • 推荐动作:该PR值得快速浏览,重点关注pytest_runtest_makereport钩子的实现方式,以及文件名清洗和导入结构调整的设计决策。对于CI基础设施维护者,可借鉴其日志收集机制以优化其他项目的测试调试流程。

功能与动机

根据PR body描述,动机是“增强CI调试能力,通过收集详细的pytest失败日志,减少不稳定或失败用例的排查时间”。这旨在解决CI环境中测试失败时调试困难的问题,提升开发和维护效率。

实现拆解

  1. 新增pytest钩子实现日志收集:在tests/conftest.py中新增pytest_runtest_makereport钩子,捕获测试失败时的错误信息,并写入FD_LOG_DIR目录下的日志文件,文件名基于测试用例名生成。
  2. 调整conftest.py导入和结构:将e2e.utils.serving_utils导入移至文件顶部,并添加日志目录创建逻辑到pytest_configure中,同时重构代码注释以提升可读性。
  3. 禁用不稳定测试文件:将tests/e2e/4cards_cases/test_Qwen3_30b_tp4.py重命名为_test_Qwen3_30b_tp4.py,以禁用该不稳定测试。
  4. 更新CI脚本排除干扰:修改scripts/coverage_run.sh,在grep命令中添加--exclude="pytest_*_error.log"选项,避免日志收集产生的错误日志干扰CI输出。

关键文件:

  • tests/conftest.py(模块 测试框架;类别 test;类型 test-coverage;符号 pytest_configure, pytest_collection_modifyitems, pytest_runtest_makereport, FDRunner): 核心变更文件,新增pytest钩子实现日志收集,并调整导入和配置逻辑。
  • tests/e2e/4cards_cases/_test_Qwen3_30b_tp4.py(模块 端到端测试;类别 test;类型 rename-or-move): 通过重命名禁用不稳定的测试文件,避免CI失败干扰。
  • scripts/coverage_run.sh(模块 脚本工具;类别 infra;类型 configuration): 更新CI脚本以排除pytest错误日志,避免干扰CI输出。

关键符号:pytest_runtest_makereport, pytest_configure, pytest_collection_modifyitems

关键源码片段

tests/conftest.py

核心变更文件,新增pytest钩子实现日志收集,并调整导入和配置逻辑。

import os
import re
import pytest@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
    捕获失败测试用例并将错误日志保存到FD_LOG_DIR。
    仅记录测试执行阶段的失败,不包含setup/teardown阶段。
    """
    outcome = yield
    report = outcome.get_result()
​
    if report.when == "call" and report.failed:
        # 从item.nodeid提取测试用例名,并清洗非法字符以确保文件名安全
        raw_name = item.nodeid.split("::", 1)[-1]
        case_name = re.sub(r'[\\/:*?"<>|]', '_', raw_name) # 替换特殊字符为下划线
​
        log_dir = os.environ.get("FD_LOG_DIR", "log")
        log_file = os.path.join(log_dir, f"pytest_{case_name}_error.log")
​
        # 将错误信息写入日志文件
        with open(log_file, "w", encoding="utf-8") as f:
            f.write(f"Test failed: {item.nodeid}\n")
            if call.excinfo is not None:
                f.write(f"Exception: {call.excinfo}\n")
            if report.longrepr is not None:
                f.write(f"Traceback: {report.longrepr}\n")

评论区精华

  1. 文件名安全性问题:PaddlePaddle-bot指出case_name未做安全清洗,参数化测试中含/等字符可能导致日志写入失败,建议添加清理逻辑。作者在后续提交中通过正则表达式修复了此问题。
  2. 导入风险疑问:PaddlePaddle-bot询问将e2e.utils.serving_utils提升为顶层导入是否会导致非e2e测试的collection失败,但未得到明确回复,此疑虑未解决。
  3. 脚本选项位置建议:PaddlePaddle-bot建议将--exclude选项放在位置参数前以避免POSIX兼容性问题,但PR中未采纳此建议。
  4. 临时测试修改:Review中发现了多个测试文件中的断言被反转为!=,这是用于验证日志收集功能的临时修改,作者在后续提交中移除了这些调试代码。
  • 文件名安全性清洗 (correctness): 作者在后续提交中添加了正则表达式清洗逻辑,替换非法字符为下划线。
  • 顶层导入风险 (design): 未得到明确回复,疑虑未解决,可能依赖环境配置。
  • 脚本选项位置 (style): PR中未采纳此建议,选项仍放在位置参数后。

风险与影响

  • 风险:1. 导入依赖风险conftest.py中顶层导入e2e.utils.serving_utils,若该模块在非e2e测试环境中不可用,可能导致所有测试的collection阶段失败。
    2. 日志文件路径安全:尽管已添加文件名清洗,但case_name清洗逻辑可能未覆盖所有边缘情况,如过长文件名或特殊编码字符,仍可能引发文件操作异常。
    3. CI脚本兼容性coverage_run.sh--exclude选项放在位置参数后,在严格POSIX环境下可能失效,导致错误日志未被排除,干扰CI输出。
    4. 测试覆盖影响:禁用test_Qwen3_30b_tp4.py可能降低对该模型特定配置的测试覆盖,需确保其他测试能补偿。
  • 影响:1. 对用户影响:无直接影响,主要面向开发者和CI维护者,提升测试失败时的调试效率。
    2. 对系统影响:新增日志文件可能增加磁盘使用量,但通过环境变量FD_LOG_DIR可控制目录,影响有限。
    3. 对团队影响:简化CI故障排查流程,减少手动日志收集时间,提升开发迭代速度。
  • 风险标记:导入依赖风险, 文件路径安全, 脚本兼容性

关联脉络

  • PR #7429 [CI] Add approval check for logging-related modifications: 同属CI和日志相关改进,涉及脚本和审批流程优化。
  • PR #7190 [Feature] implement log channel separation and request log level system: 涉及日志系统增强,本PR的日志收集机制可视为其补充。

参与讨论