Repository navigation
Code Analysis Pluggy
Tu Shaokun edited this page Nov 5, 2026
·
1 revision
上游 PR #695 修复了一个筛选错误。插件可以把名为 alias 的方法注册到另一个名字的钩子。旧代码用插件上有没有同名属性来决定该排除谁,漏掉了这个别名。新版改查该钩子已经登记的插件对象。上游差异与回归测试
| 要查的事 | #695 后版源码 |
|---|---|
| 登记的是哪个方法? |
_manager.py:221–240 读取标记,创建 HookImpl,用 specname 或属性名选定钩子。_hooks.py:696 保存函数。 |
| 谁从名单里排除插件? |
_manager.py:603–619 用当前注册对象筛选。_hooks.py:650–655 在读取实现列表时排除这些对象。 |
| 到底从哪里执行? |
_hooks.py:530–545 把名单交给执行器,_manager.py:173–184 默认转交给 _multicall。普通插件方法在 _callers.py:126 被调用。_hooks.py、_callers.py 在这次 PR 中均未改动。 |
用同一公开 Labs 提交(bf0a29e7)检查 pluggy 变更前后,每版覆盖 31 个 Python 文件,记录 235 条导入。逐处用固定源码和 Python 模块查找结果比对,50 个指向 pluggy 或其子模块的绝对导入位置全部吻合,其中 46 个在测试目录。Labs 能用限定名区分新测试里的同名方法。Python 调用记录为 0,单凭这些输出无法确定某次分派实际选择的插件。注册与筛选 · 普通调用位置
在同一测试文件搜索 def he_method1( 有 52 个声明命中。沿变更中实际使用的字段查找注册、筛选和调用,9 处源码命中分布在三个实现文件,其中包括包装器的其他分支。这个对照记录可到达的位置,未测量读者用时。本地双版本研究工具 c3dddd5c 的变更选择限于 JS/TS,在这次纯 Python 变更上选中 0 个文件。
先写下源码预期,再把同一探针分别放在三个干净进程中运行。另一个也叫 run 的方法被注册给 other,三个版本都只在调用 other 时执行它。
| 运行输入 | #695 前 2e916c21
|
#695 后 3d5417a4
|
上游后续 #827 的固定提交 f3974a10
|
|---|---|---|---|
| 已注册别名与普通实现,再排除别名插件 | keep、alias | keep | keep |
| 先把普通插件列为排除项,再注册它 | 空 | late | 空 |
| 不筛选,调用已注册的普通插件 | late | late | late |
同一行分派代码可以执行上述任何一组结果。判断具体目标要知道钩子名、注册对象,以及创建筛选器时插件是否已经注册。#827 一列只记录该提案固定提交的运行行为。后续提案
用 Git 和 Python 3.13 复现
在临时目录抓取固定提交。脚本直接读取各版本的 src,只依赖 Python 标准库。每个提交运行一个独立进程。
set -eu
w=$(mktemp -d)
git init -q "$w/source"
cat > "$w/probe.py" <<'PYPROBE'
import json
from pathlib import Path
import sys
root = Path(sys.argv[1]).resolve()
src = root / "src"
sys.path.insert(0, str(src))
import pluggy
assert Path(pluggy.__file__).resolve() == src / "pluggy" / "__init__.py"
spec = pluggy.HookspecMarker("trial")
impl = pluggy.HookimplMarker("trial")
class Hooks:
@spec
def run(self):
pass
@spec
def other(self):
pass
class Alias:
@impl(specname="run")
def alternate(self):
return "alias"
class Keep:
@impl
def run(self):
return "keep"
class Decoy:
@impl(specname="other")
def run(self):
return "decoy"
class Late:
@impl
def run(self):
return "late"
def manager():
pm = pluggy.PluginManager("trial")
pm.add_hookspecs(Hooks)
return pm
pm = manager()
alias = Alias()
for item in (alias, Keep(), Decoy()):
pm.register(item)
registered = pm.subset_hook_caller("run", [alias])
late_pm = manager()
late = Late()
before_registration = late_pm.subset_hook_caller("run", [late])
late_pm.register(late)
out = {
"full": pm.hook.run(), "subset": registered(), "other": pm.hook.other(),
"lateSubset": before_registration(), "lateFull": late_pm.hook.run(),
}
assert out["full"] == ["keep", "alias"] and out["other"] == ["decoy"]
assert out["lateFull"] == ["late"]
print(json.dumps(out, sort_keys=True))
PYPROBE
for sha in 2e916c21f3e5b60865d3f1f2d6f0932d61c77707 3d5417a4eff449e17e5bb3e1545b63cbf0e0c768 f3974a104f4112ac14e98bd04f0430408f536d8e; do
git -C "$w/source" fetch -q --depth=1 https://github.com/pytest-dev/pluggy.git "$sha"
git -C "$w/source" checkout -q --detach FETCH_HEAD
python3.13 -I -B -S "$w/probe.py" "$w/source"
done