当前位置: 首页 > news >正文

【Bug已解决】Stale `xfail` markers in `langchain-core` tests: `test_convert_to_openai_function_nested_v2…

【Bug已解决】Stalexfailmarkers inlangchain-coretests:test_convert_to_openai_function_nested_v2andtest_sync_in_sync_lambdaspass but are marked expected-to-fail 解决方案

一、现象长什么样

langchain-core的测试套件里,有两个测试用例被打了@pytest.mark.xfail(预期失败),但实际上它们早就修好了、现在会通过

  • test_convert_to_openai_function_nested_v2
  • test_sync_in_sync_lambdas

它们的表现如下:

  • 在 CI 里跑pytest,这两个用例不是绿色的 PASS,也不是红色的 FAIL,而是被标记为XPASS(unexpectedly passing,意外通过);
  • 由于 pytest 默认把xfail_strict=False,XPASS 会被当成"通过"处理,于是问题被悄悄掩盖——测试报告是全绿的,没人发现这俩标记已经过时;
  • 一旦哪天有人把xfail_strict=True打开(这是很多项目为了"xfail 不能偷偷变绿"而推荐的做法),这两个用例会立刻变红,CI 直接挂掉,而且报错信息对排查者极不友好:"expected to fail but passed";
  • 更糟的是,因为标了xfail,新来的贡献者会误以为"这功能本来就是坏的",从而绕开、甚至停止修复相关逻辑,导致技术债被长期保留。

一句话:xfail标记标错了对象,把已经通过的测试当成了已知失败,形成"僵尸标记"。

二、背景

xfail(expected failure)是 pytest 提供的一个机制,用来标记"我知道它会失败,先别让它红"。它在两种场景下是合理的:

  1. 上游依赖有 bug,暂时绕不过,先xfail等上游修;
  2. 一个尚不支持的特性,先xfail锁住,避免回归时无声无息。

xfail是一把双刃剑:它本质上是在说"这个测试现在不该绿"。一旦底层代码被修复、功能被实现,标记却没同步摘掉,测试就会从 XFAIL(符合预期)变成 XPASS(不符合预期)。XPASS 在严格模式下直接失败,在非严格模式下则只是静默通过——两种都违背了xfail的初衷。

test_convert_to_openai_function_nested_v2是验证嵌套结构(nested v2)转 OpenAI function 的工具;test_sync_in_sync_lambdas是验证在同步 lambda 里调用同步方法不会死锁/报错。这两个能力在langchain-core的某个版本之后就已经正常工作了,但当时为了赶发布临时打的xfail没被清理,于是长期残留。

三、根因

根因是测试维护纪律缺失,而不是业务逻辑问题:

  • 当初这两个测试因为某种临时原因(可能是依赖版本、可能是实现未完成)被标xfail
  • 后续 PR 修复了底层实现,让测试能过了,却只改了源码、没回头清理测试标记
  • CI 的xfail_strict没打开,XPASS 被当成 PASS 吞掉,没有警报,于是标记一直"活着";
  • 没有定期跑pytest --strict-markersxfail_strict=True的守护,导致僵尸标记无法被发现。
# 残留的错误标记(示意) @pytest.mark.xfail( reason="nested v2 conversion not supported yet", strict=False, ) def test_convert_to_openai_function_nested_v2(): ...
# 实际上功能早已支持,标记应被删除 def test_convert_to_openai_function_nested_v2(): ...

四、最小可运行复现

下面用一个最小例子演示"僵尸 xfail"如何从静默 XPASS 变成严格模式下的红色失败:

import pytest def _add(a: int, b: int) -> int: # 假设这个功能后来已经被正确实现 return a + b @pytest.mark.xfail(reason="add 还没实现", strict=False) def test_add_passes_but_marked_xfail(): # 功能其实已经好了,但标记没摘 -> XPASS(被当 PASS 吞掉) assert _add(1, 2) == 3 @pytest.mark.xfail(reason="add 还没实现", strict=True) def test_add_strict_xfail(): # 打开 strict 后,XPASS 直接变红 assert _add(1, 2) == 3

运行:

$ pytest -q # 默认 strict=False:test_add_passes_but_marked_xfail 显示 XPASS,整体 PASSED # 若 pytest.ini 设 xfail_strict=true:test_add_strict_xfail 直接 FAILED (XPASS)

这正是langchain-core里那两个用例的真实处境:平时静默 XPASS,一旦严格化就爆红。

五、解决方案(第一层:最小直接修复)

最小修复就是删掉过时的xfail标记,让测试回归正常的 PASS/FAIL 语义:

# libs/core/tests/unit_tests/test_to_openai.py def test_convert_to_openai_function_nested_v2(): result = _convert_to_openai_function_nested(sample_nested_v2) assert result["parameters"]["type"] == "object" assert "nested" in result["parameters"]["properties"] # libs/core/tests/unit_tests/test_runnables.py def test_sync_in_sync_lambdas(): chain = RunnableLambda(lambda x: x + 1) assert chain.invoke(1) == 2

如果某个测试确实还想保留"软失败"的弹性(比如依赖外部服务偶尔抖动),应改用@pytest.mark.flaky或显式的try/except + pytest.skip,而不是用xfail表达"应该会过"。

六、解决方案(第二层:结构化改进)

为防止以后再出现僵尸xfail,应当把"标记清理"纳入流程约束,并提供一个可复用的审查工具。核心思路:在 CI 里强制xfail_strict=True,并提供一个扫描脚本,找出所有 XPASS 的用例清单。

from dataclasses import dataclass from pathlib import Path import re from typing import List @dataclass(frozen=True) class LangChainXfailMarkerPolicy: """xfail 标记审查策略:扫描测试文件,列出所有 xfail 装饰器。 配合 xfail_strict=True 使用,可让"僵尸 xfail"在 CI 直接变红, 从而逼迫贡献者及时清理。 """ root: Path def list_xfail_markers(self) -> List[str]: pattern = re.compile(r"@pytest\.mark\.xfail") hits: List[str] = [] for path in self.root.rglob("test_*.py"): for lineno, line in enumerate(path.read_text().splitlines(), 1): if pattern.search(line): hits.append(f"{path}:{lineno}: {line.strip()}") return hits def audit(self) -> str: markers = self.list_xfail_markers() if not markers: return "OK: 未发现任何 xfail 标记。" return "需要人工复核以下 xfail 标记是否仍合理:\n" + "\n".join(markers) def main() -> None: policy = LangChainXfailMarkerPolicy(root=Path("libs/core/tests")) print(policy.audit()) if __name__ == "__main__": main()

同时,在pyproject.toml/pytest.ini里加上:

[pytest] xfail_strict = true

这样,任何"意外通过"的xfail都会立即让 CI 失败,迫使开发者要么删除标记、要么真的让它失败,杜绝静默残留。

七、解决方案(第三层:断言 / CI 守护)

用一条 CI 规则把"禁止僵尸 xfail"锁死:

# .github/workflows/tests.yml (节选) - name: Run tests with strict xfail run: pytest -q --xfail-strict

并提供回归测试,验证清理后的用例能正常 PASS:

import pytest from langchain_core.utils.function_calling import convert_to_openai_function def test_convert_to_openai_function_nested_v2(): # 确认嵌套 v2 结构能正确转换,且不再被 xfail 屏蔽 spec = { "name": "f", "parameters": { "type": "object", "properties": { "outer": { "type": "object", "properties": {"inner": {"type": "string"}}, } }, }, } converted = convert_to_openai_function(spec) assert converted["parameters"]["properties"]["outer"]["properties"]["inner"]["type"] == "string" def test_sync_in_sync_lambdas(): from langchain_core.runnables import RunnableLambda chain = RunnableLambda(lambda x: x * 2) assert chain.invoke(21) == 42

pytest --xfail-strict时,这两个用例应当干净地显示PASSED,不再有XPASS出现。

八、排查清单

  • langchain-core测试目录里grep -rn "xfail"找出所有标记。
  • pytest --xfail-strict,看是否有用例报XPASS(strict 下为 FAILED)。
  • 对每个 XPASS 用例,判断功能是否已实现:已实现则删标记,未实现则保留并补全 reason。
  • pytest.ini里打开xfail_strict = true,让僵尸标记今后无法静默存活。
  • 用上面的LangChainXfailMarkerPolicy脚本定期扫描,作为 PR 检查的一部分。
  • 新加xfail时,必须在 reason 里写清"为什么失败、预计何时摘掉"。

九、小结

这两个xfail标记早已过时——底层功能修复后,测试会意外通过(XPASS),却因为xfail_strict=False被静默吞掉,形成僵尸标记,既掩盖了 CI 真实状态,也让贡献者误判功能状态。最小修复是删掉过时标记;更稳妥的做法是在pytest.ini打开xfail_strict=True,并配合LangChainXfailMarkerPolicy这样的扫描脚本,把"xfail 不能偷偷变绿"写进 CI 守护,从根本上杜绝同类问题复发。

http://www.cnnetsun.cn/news/4096495.html

相关文章:

  • AI Agent学习笔记(20260817)
  • AI智能体协作中的耶克斯-多德森曲线:环境压力如何影响多智能体系统性能
  • 基于ReAct智能体框架的高熵合金设计:从相预测到主动设计
  • SketchUp STL格式转换终极上手指南:一个免费插件5步打通3D打印全流程
  • 大模型为何用知识换推理?从GLM-5.2、Qwen3.5看AI进化新范式
  • Claude Code的上下文管理机制
  • 4 个问答吃透右键菜单优化:用 ContextMenuManager 让卡顿菜单恢复流畅
  • 1、在线教育简介
  • 博克CAD定数化制版实战:从原理到短袖案例全解析
  • AgentFlow:静态分析AI智能体依赖关系,构建可视化架构图
  • NCM转MP3完整教程:ncmdump快速解锁网易云加密音乐的实用指南
  • 从预测蛋白质到发现新药:AI 正在重写“科研“这件事本身
  • VideoDownloadHelper 完整上手指南:开源免费的 Chrome 视频下载插件,保姆级教程带你轻松抓取网页视频
  • 编程语言性能深度解析:从原理到场景的实战选型指南
  • 2026年8月青岛到广州物流行业研究报告:深度剖析发展态势
  • 2024 春招市场行情报告:鸿蒙人才遭“爆抢”的背后
  • 2026年8月:联想官方授权售后保障服务
  • 编程语言如何塑造开发者思维:从范式到实践
  • Wallpaper Engine 资源打不开?repkg 解包转换终极指南:一行命令提取 PKG、TEX 转 PNG
  • 基于nRF54L20实现8K回报率鼠标与LVGL动图刷屏的嵌入式高性能开发实践
  • MMD手办风格渲染实战:从原理到配置,打造虚拟角色实体质感
  • 智能编程工具更新后,先验证哪些基础场景
  • DM使用TRUNCATE详解:高效清空表数据的最佳实践
  • 用智能工具管项目,先把一个决策流程跑通
  • 端侧智能推理升级后,先核对驱动、内存和回退
  • 选智能工具链,先拿一个工作流做验证
  • RT-Thread物联网操作系统:从内核到生态的嵌入式开发实战指南
  • 后端工程师入门技术栈梳理
  • FreeRTOS阻塞链表实现:任务调度与内核唤醒机制详解
  • Unity游戏如何自动翻译成中文?10分钟上手XUnity.AutoTranslator免费翻译插件