用 py-fuzzer 对 Ruff 进行随机模糊测试从种子生成到 Bug 最小化复现的完整指南【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读python/py-fuzzer是 Ruff 仓库中配套的 Python 模糊测试工具它通过pysource-codegen随机生成语法合法的 Python 源码驱动 Ruff以及 ty 类型检查器可执行文件自动发现解析器崩溃、panic 等缺陷并用pysource-minimize将触发代码自动最小化为最小复现用例。本文以 python/py-fuzzer/README.md 为核心骨架结合 fuzz.py 源码逐层拆解其工作原理帮助你掌握完整的模糊测试工作流环境准备、种子选择、新旧 Bug 判定、批量并发运行与结果解读。一、py-fuzzer 是什么根据 README 的定义py-fuzzer 是一个用于在随机生成但语法合法的 Python 源码文件上运行 Ruff 可执行文件的模糊测试脚本。它属于 Python 生态一侧的模糊测试工具与仓库fuzz/目录下基于cargo-fuzz/libFuzzer 的 Rust 原生模糊器互补后者做变异式模糊而 py-fuzzer 走生成式路线——每次用一个确定性的种子seed生成一份全新的、语法上一定合法的 Python 代码再交给 Ruff 处理通过代码一定能被解析这一前提来反推如果 Ruff 在这种输入上仍然出错非零退出码就说明解析器存在缺陷。它本身是仓库中的一个独立 Python 包声明在 python/py-fuzzer/pyproject.toml包名为py-fuzzer版本号0.0.0只供 Ruff 项目自身做模糊测试使用并不是对外发布的命令行工具。核心依赖包括依赖用途uv.lock 中锁定版本pysource-codegen按种子生成随机但语法合法的 Python 源码0.7.1pysource-minimize把触发 Bug 的代码最小化为最小复现0.10.1rich-argparse为 CLI 帮助提供富文本格式1.7.1ruff作为默认基线版本来源0.14.1termcolor终端彩色输出—版本信息见 uv.lock 的锁定记录。项目要求 Python 3.12并通过[project.scripts]注册了fuzz fuzz:main入口点即安装后可直接使用fuzz命令。二、环境准备安装 uv 并运行 fuzzer运行 py-fuzzer 的前提是安装uvREADME 明确要求。官方推荐的运行方式是使用uv run --project它会让 uv 尊重脚本自带的 lockfilefuzz.py 的模块文档字符串中特别强调使用uv run --project而不是uvx --from正是因为前者会读取项目的锁定依赖版本保证每次运行环境一致。在仓库根目录下查看完整帮助信息与示例uv run --project./python/py-fuzzer fuzz -h除此之外还有两种安装/运行方式# 方式一安装到当前虚拟环境后直接调用需先激活虚拟环境 uv pip install -e ./python/py-fuzzer fuzz -h # 方式二不激活虚拟环境直接临时运行 uv run --project./python/py-fuzzer fuzz -h推荐使用第二种uv run --project方式它无需激活任何虚拟环境且严格按照 uv.lock 锁定依赖可复现性最好。三、命令行参数详解py-fuzzer 的 CLI 由 parse_args 实现全部参数如下参数类型/取值说明seeds位置参数整数或0-5形式的闭区间用于生成随机代码的种子。同一个种子永远生成同一份代码FuzzResult.seed字段注释The same seed always generates the same file.因此可以精确复现问题--binruff/ty必填。指定测试哪个可执行文件取值由 Executable 枚举 约束--test-executable路径被测的可执行文件。默认会自动执行cargo build --profileprofiling构建当前检出分支的最新版--baseline-executable路径用于对比的基线可执行文件仅在同时指定--only-new-bugs时生效否则 parse_args 会直接报错拒绝启动--only-new-bugs开关只报告当前分支存在、但环境中安装的发布版不存在的新 Bug--quiet开关运行时减少终端输出只在结束时打印摘要种子参数支持两种形式单个整数如78或闭区间如0-2等价于0,1,2。区间解析由 parse_seed_argument 完成它有两个边界约束区间结束必须大于起始区间长度不得超过 10 亿1_000_000_000超出会抛出参数错误防止误输入导致内存爆炸。默认行为自动构建测试可执行文件如果没有指定--test-executableparse_args 会自动运行cargo build --profileprofiling --locked --color always --bin ruff构建产物位于target/profiling/bin。仓库根目录 Cargo.toml 中定义了对应的[profile.profiling]它继承release配置但保留完整调试符号strip false、debug full并关闭 fat LTO目的是让基准测试和模糊测试既能接近发布版性能又能保留可回溯的调试信息。--only-new-bugs 的基线回退逻辑当指定--only-new-bugs但没有提供--baseline-executable时fuzzer 会回退到当前 Python 环境里安装的发布版作为基线parse_args它通过运行ruff --version或ty --version读取已安装版本号并提示将ruffversion作为基线。这让你在本地开发分支上跑 fuzzer 时能够精确区分老版本就有的问题和你的改动新引入的问题。四、官方示例调用fuzz.py 的文档字符串给出了三个官方示例逐一解释1. 用指定种子跑 Ruff 解析器uv run --project./python/py-fuzzer fuzz --bin ruff 0-2 78 93使用种子 0、1、2、78、93 共 5 个种子生成随机代码并逐一测试 Ruff。由于种子数 ≤ 5run_fuzzer 会走顺序执行路径run_fuzzer_sequentially。2. 只报告当前分支的新 Buguv run --project./python/py-fuzzer fuzz --bin ruff 0-10 --only-new-bugs并发地对 0~10 共 11 个种子运行 fuzzer但只报告在你当前分支上存在、而基线版本不存在的新 Bug。此时种子数 5会自动切换到并发执行路径run_fuzzer_concurrently基于concurrent.futures.ProcessPoolExecutor见 fuzz.py。若遇到 CtrlC执行器会先优雅关闭shutdown(cancel_futuresTrue)再退出。3. 大规模随机抽检并静默输出uv run --project./python/py-fuzzer fuzz --bin ruff $(shuf -i 0-1000000 -n 10000) --quiet从 0~1000000 之间随机抽取 10000 个种子并发测试--quiet让终端只保留必要信息。README 和 docstring 都特别注明shuf是 Unix 特有命令Windows 用户需要自行换用等效的随机抽样方式。五、运行结果如何解读无论顺序还是并发最终都汇总到 run_fuzzer没有 Bug打印绿色No bugs found!指定基线时是No new bugs found!进程退出码为0发现 Bug打印红色Bugs found in the following seeds:及所有触发 Bug 的种子列表升序去重进程退出码为1。每个种子的中间进度由 FuzzResult.print_description 输出成功用绿色标注Ran fuzzer successfully on seed N发现 Bug 用红色标注并紧接着打印最小化后的复现代码例如The following code triggers a new parser bug: 最小化后的 Python 代码对于 ty输出会明确标注触发 panic 所用的参数组合--python-version3.10 --python-platformlinux见 ty_contains_bugOLDEST_SUPPORTED_PYTHON 3.10TY_TARGET_PLATFORM linux。选择 3.10 作为最老版本的原因在源码注释中有说明ty 支持--python-version3.8但 typeshed 只支持 3.10因此 3.10 是能有效测试的最老版本。六、底层实现原理从种子到最小复现下面顺着 fuzz_code 的主流程拆解一次完整的模糊测试周期。1. 生成随机源码对每个种子调用generate_random_code(seed)来自pysource-codegen。由于同一种子输出完全确定任何一次失败都可以通过重跑同一种子精确复现——这是种子驱动式模糊测试的核心价值。2. Bug 判定以 Ruff 为例ruff_contains_bug 用 stdin 方式运行被测的 ruff 可执行文件ruff check --config lint.select[] --no-cache --target-version py314 --preview -各参数含义--config lint.select[]关闭所有 lint 规则保证唯一的出错来源是解析器本身--no-cache禁用缓存避免代码变化被缓存掩盖与crates/ruff/src/commands/format.rs中对调试构建--no-cache的提醒同理--target-version py314以最新语法目标解析覆盖更多新语法--preview开启预览版语法特性-从标准输入读取源码。由于所有 lint 规则已被禁用退出码非 0 即视为解析器 Bugreturn completed_process.returncode ! 0。ty 侧则更严格ty_contains_bug 认为退出码只要不在{0, 1, 2}之内就算 Bug即捕获的是类型检查器内部 panic/崩溃而非普通的类型错误。3. 新 Bug 的判定contains_new_bug 定义了新 Bug同一份代码在被测版本上触发错误、在基线版本上不触发错误。这要求同时持有两个可执行文件test_executable默认是cargo build --profileprofiling构建出的当前分支版本和baseline_executable默认回退到环境中的已安装发布版。4. 自动最小化复现一旦判定为 Bugfuzzer 会将该代码触发 Bug这一谓词包装成回调partial(contains_bug, ...)交给pysource-minimize的minimize()做最小化产出能触发 Bug 的最短代码。若最小化失败抛CouldNotMinimizefuzz_code 会做一次防御性校验用ast.parse重新解析原始代码确认问题是否出在 codegen/minimize 工具本身理论上这两个工具不应产出语法非法的代码校验无误则退回使用原始代码作为复现用例。5. 顺序 vs 并发调度策略在 run_fuzzer 中种子数 ≤ 5 走顺序循环 5 走ProcessPoolExecutor进程池并发每个种子一个独立进程配合as_completed边跑边汇总遇到KeyboardInterrupt会取消未完成任务并优雅退出。七、与仓库 Rust 侧模糊测试的分工仓库还提供了一套 Rust 原生的模糊测试基础设施见 fuzz/README.md 与 fuzz/fuzz_targets 目录它基于cargo-fuzz/libFuzzer 做变异式模糊harness 包括ruff_parse_simple解析unparse 不崩溃、ruff_parse_idempotency解析幂等性、ruff_fix_validity修复不引入新错误、ruff_formatter_idempotency/ruff_formatter_validity格式化器稳定性等。两者定位互补Rust 侧cargo-fuzz对任意字节输入做变异覆盖更广、更野但可能生成语法非法的代码py-fuzzerPython 侧从零生成语法合法的代码专门针对合法代码却被 Ruff 解析失败/崩溃这类高价值缺陷——这正是解析器最不该出问题的场景。在实际开发中两者常配合使用先用 py-fuzzer 的大规模种子扫描发现合法代码触发的解析问题再用 Rust 侧 fuzzer 的 corpus 机制持续回归。八、最佳实践与注意事项综合 README、docstring 与源码给出以下实践建议善用种子可复现性提交 Issue 或调试时保留触发 Bug 的种子因为同一个种子永远生成同一份代码任何人重跑该种子即可复现先用小规模顺序跑种子数 ≤ 5 时顺序执行便于逐个观察每个种子的输出确认流程正常后再放大到并发规模开发分支用--only-new-bugs配合自动构建的--test-executable当前分支与已安装发布版基线快速过滤历史存量问题聚焦自己引入的新缺陷大规模随机抽检$(shuf -i 0-1000000 -n 10000)适合 CI 前的例行巡检配合--quiet减少输出噪音注意平台差异shuf仅 Unix 可用uv run --project会尊重项目 lockfile务必使用该方式以保证依赖一致退出码语义脚本以退出码0无 Bug和1有 Bug区分结果可直接接入 CI 门禁。结语py-fuzzer 以随机但合法的生成式思路为 Ruff 解析器与 ty 类型检查器提供了一条低成本、高覆盖的缺陷发现路径种子驱动保证可复现自动最小化把复杂的触发输入压缩成可直接提交的最小复现用例--only-new-bugs让开发者能在自己的分支上精准定位新引入的问题。配合仓库 Rust 侧的 cargo-fuzz 基础设施它构成了 Ruff 质量保障体系中生成与变异两条互补的模糊测试防线。如果想进一步深入可以直接阅读 fuzz.py 全文仅约 460 行以及 pyproject.toml 中对该脚本自身的 lint/type-check 约束——这份代码本身也是高质量 Python CLI 工具的一个不错范本。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考