
Ansible hacking/azp 工具实战从 Azure Pipelines 下载 CI 结果并分析 Incidental 代码覆盖率【免费下载链接】ansibleAnsible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.项目地址: https://gitcode.com/GitHub_Trending/ans/ansible本文以 Ansible 仓库 hacking/azp 目录下的 CI 辅助脚本为主体讲清四个工具run.py、download.py、get_recent_coverage_runs.py、incidental.py各自解决什么问题并完整走通「获取最近覆盖率运行 → 下载 CI 结果 → 分析 incidental 覆盖率报告 → 补齐测试缺口」这条工作流。读完本文你将能够独立下载 Azure Pipelines 的覆盖率数据定位那些顺带覆盖了核心代码的测试并理解覆盖率报告中山丘弧线arc标记的含义。目录概览四个脚本各管一件事hacking/azp/README.md 声明该目录包含以下脚本每个脚本的职责与源码中的实现一一对应脚本职责关键实现入口download.py从 CI 下载运行结果解析 run id下载 artifacts/日志/元数据get_recent_coverage_runs.py获取最近覆盖率测试运行的 CI URL 与状态轮询 pipeline 20 的运行列表incidental.py基于 CI 数据生成 incidental 覆盖率报告组合ansible-test coverage analyze targets子命令run.py在 CI 上发起新的运行调用 Azure Pipelines REST API 创建 runrun.py手动触发一次 CI 运行run.py 通过 Azure Pipelines REST API 发起新运行源码中的参数定义hacking/azp/run.py#L58-L77如下-p, --pipeline-id要触发的 pipeline默认值为20Ansible 官方 pipeline--ref要运行的 git 引用分支/标签--env KEY VALUE传递给运行的环境变量可重复出现nargs2, actionappend。使用前必须设置AZP_TOKEN环境变量否则脚本直接报错退出见 hacking/azp/run.py#L50-L53export AZP_TOKENyour token hacking/azp/run.py --ref devel --env KEY VALUEstart_run函数会向https://dev.azure.com/ansible/ansible/_apis/pipelines/id/runs发送 POST 请求并把返回的运行信息以 JSON 打印到终端。源码注释中还留有一条 TODO提示 Dev 团队缺少 AZP token、该路径未被充分测试使用时如遇到认证问题属于已知情况。第一步获取最近的覆盖率运行覆盖率工作流的第一步是找到最近一次带代码覆盖率的 CI 运行。仓库每天自动在 Azure Pipelines 上执行一次全量测试并开启代码覆盖率get_recent_coverage_runs.py 负责列出这些运行hacking/azp/get_recent_coverage_runs.py optional branch name分支名默认为devel对应源码中的BRANCH devel若sys.argv有值则覆盖见 hacking/azp/get_recent_coverage_runs.py#L33-L38。从源码可以确认它的筛选逻辑hacking/azp/get_recent_coverage_runs.py#L41-L77拉取 pipeline 20 最近至多 1000 条运行过滤出指定分支refs/heads/branch的运行过滤掉超过 24 小时MAX_AGE datetime.timedelta(hours24)的运行只保留 artifact 名称以Coverage开头的运行——即真正开启了覆盖率采集的运行。输出格式为每行一条带彩色 PASS/FAIL 标记与 Azure Pipelines 构建 URL进行中的运行单独列在末尾。源码中还有一段兼容性处理遇到使用容器资源的老运行会因 Azure API 序列化错误返回 500脚本会直接break停止继续翻页避免无谓请求失败。拿到 run idURL 中的buildId后即可进入下一步下载结果。第二步下载 CI 结果到本地hacking/azp/download.py run id --artifacts --run-metadata -v是 README 给出的标准用法。其完整参数解析逻辑见 hacking/azp/download.py#L55-L119参数说明RUN位置参数AZP 运行 id或直接的构建 URL如https://dev.azure.com/ansible/ansible/_build/results?buildId14075。run_id_arg用正则提取其中的纯数字 id-v, --verbose打印实际下载了什么-t, --testdry-run只显示将下载的内容而不真正下载-p, --pipeline-idpipeline id默认20仅影响 run 元数据请求的 URL--artifacts下载 artifactszip 包解压到运行 id 命名的目录--console-logs下载各 job 的控制台日志--run-metadata下载运行元数据并保存为run.json--all等价于同时打开上面三项--match-artifact-name只下载文件名匹配该正则的 artifact--match-job-name只处理 job 名父job名 子job名匹配该正则的内容结果统一落到以 run id 命名的目录output_dir run id例如# 结果下载到当前目录下的 ansible/ansible 路径14075 替换为你要下载的运行号 hacking/azp/download.py 14075 --artifacts --run-metadata -v从源码看下载流程为--run-metadata时请求 pipeline run API把完整 JSON 写入run id/run.json请求构建的 timeline 接口把 job/step 构建出父子关系树再按--match-job-name决定哪些子树允许下载--artifacts时遍历 artifact 列表逐个下载 zip 并在内存中用zipfile解压到输出目录--console-logs时沿 timeline 的父子链拼接出父job 子job step风格的日志文件名把路径分隔符替换为_逐个保存。注意两点前提run.json必须存在后续incidental.py依赖它读取被测 commit sha 与运行结果见下文至少选择--artifacts/--run-metadata/--console-logs之一否则脚本以parser.error报错退出。什么是 Incidental Code Coverageincidental.py 是整套工具中技术含量最高的部分理解它的前提是先理解 README 中定义的 incidental 概念当一个测试在测试 A 代码的同时非预期地顺带覆盖了一部分 B 代码就产生了 incidental 测试与代码覆盖率。原文档给的例子dnf集成测试本意是测dnf模块但过程中同时使用并无意测试了file模块。这个概念与 Ansible 模块化历史强相关。README 说明在把模块和插件迁移进 collections 的过程中发现了一些独占性 incidental 覆盖——即即将随迁移出仓库的测试所覆盖的代码在迁移后没有任何剩余测试再覆盖。为避免覆盖丢失这些集成测试目标被加上incidental_前缀保留在仓库中其依赖的插件也被保留在 test/support 目录下。当前仓库中可以看到这样的存量目标例如 test/integration/targets/incidental_win_reboot。这些 incidental 测试的长期目标是被有意的intentional测试替代随着有意测试的增加incidental 测试提供的独占覆盖会下降降到零后即可删除而不损失任何测试覆盖。减少 Incidental 覆盖的完整工作流README 给出了四步流程下面逐步展开并补充源码依据。步骤 1获取最近的覆盖率运行 URL即上文get_recent_coverage_runs.py的用法。步骤 2下载覆盖率数据即上文download.py的用法。步骤 3分析每个测试覆盖的代码# 确认 ansible-test 在 $PATH 中 source hacking/env-setup # 用实际下载结果的目录名替换 14075/ hacking/azp/incidental.py 14075/incidental.py的参数全集hacking/azp/incidental.py#L55-L107参数说明result位置参数从 Azure Pipelines 下载的结果目录必须是目录否则报错--output报告输出目录默认test/results/.tmp/incidental--sourceAnsible 源码 git 仓库路径默认取脚本所在仓库根--skip-checks跳过一致性检查仅供调试--ignore-cache忽略已缓存的中间文件-v, --verbose提高输出详细度--result-sha覆盖从run.json中读取的结果 sha--targets待分析 target 的正则默认^incidental_--plugin-path改为对指定插件路径报告其自身测试缺失的 incidental 覆盖与--targets互斥从源码看其内部执行链路incidental_report函数hacking/azp/incidental.py#L131-L257读取运行元数据CoverageData从结果目录中的run.json取出被测 commitresources.repositories.self.version与运行结果result并从 glob 到的各 job 产物*/coverage-analyze-targets.json收集覆盖率数据。这正是download.py --run-metadata --artifacts必须同时使用的底层原因一致性检查被测 commit 必须在本仓库可git show否则提示先更新你的源码仓库若运行结果不是succeeded则拒绝继续可用--skip-checks降级为警告若无coverage-analyze-targets.json则报错提示确认下载的是覆盖率运行的结果生成哈希子目录对所有输入覆盖率文件路径做 SHA-256得到test/results/.tmp/incidental/{hash}/这与 README 中{hash}基于生成报告所用输入文件的描述一致调用 ansible-test 子命令做集合运算CoverageTool类封装了对ansible-test coverage analyze targets的调用依次执行combine合并各 job 报告、filter按 target 保留/排除得到only-target.json与without-target.json、missing求差集--only-gaps只保留缺口、expand展开行号区间。这套子命令的实现在 test/lib/ansible_test/_internal/commands/coverage/analyze/targets/ 下包含combine.py、filter.py、missing.py、expand.py、generate.py等模块求独占覆盖默认模式下exclusive missing(only_target, without_target)即只有该 target 覆盖、其他所有 target 都不覆盖的代码行/弧生成文本报告对每个 target 写出reports/target.txt并在终端打印汇总行target: N arcs, M lines, K files - report path。所有中间产物通过cached()辅助函数做文件级缓存重复运行可跳过已生成的文件。另外注意源码中两处硬编码的排除项test/support/下的测试支持插件与lib/ansible/module_utils/six/不参与分析后者被注释说明会报告虚假的注释行覆盖。步骤 4编写有意测试补齐缺口根据test/results/.tmp/incidental/{hash}/reports/下的报告为新覆盖的代码创建新测试或扩展现有测试。随着该过程循环进行独占覆盖会逐步下降当某个 incidental 测试不再提供独占覆盖时即可删除。README 特别警告一次只能删一个 incidental 测试因为删掉一个后原本由它分担覆盖的代码可能使另一个测试获得新的独占覆盖。针对插件的覆盖率缺口分析incidental 分析不限于incidental_前缀的测试某个 filter 插件自身测试覆盖不全时缺口可能由无关测试顺带填补incidental.py同样能定位这些缺口。用法是在步骤 3 中加--plugin-path {path_to_plugin}可对任意多个插件重复执行。一次分析所有 filter 插件的示例README 原文find lib/ansible/plugins/filter -name *.py -not -name __init__.py -exec hacking/azp/incidental.py 14075/ --plugin-path {} ;指定--plugin-path后脚本行为切换为missing模式missing True把插件路径映射到其集成测试 target 名get_target_name_from_plugin_path如lib/ansible/modules/dnf.py→dnflib/ansible/plugins/filter/xxx.py→filter_xxx见 hacking/azp/incidental.py#L259-L280然后计算missing(without_target, only_target)即该插件自身测试未覆盖、但其他测试覆盖了的缺口。即使该插件没有对应测试 target也会生成一份报告说明缺失的覆盖。README 同时提醒报告不标注这些 incidental 覆盖来自哪个测试。如何阅读覆盖率报告每行被覆盖的代码都会出现在报告中左列是源码行号若是 Python 代码行尾注释还会标注涉及的覆盖弧arc。README 给出的真实报告样例Target: incidental_win_psexec GitHub: https://github.com/ansible/ansible/blob/6994ef0b554a816f02e0771cb14341a421f7cead/test/integration/targets/incidental_win_psexec Source: lib/ansible/executor/task_executor.py (2 arcs, 3/1141 lines): GitHub: https://github.com/ansible/ansible/blob/6994ef0b554a816f02e0771cb14341a421f7cead/lib/ansible/executor/task_executor.py 705 if rc in result and result[rc] not in [0, 0]: ### (here) - 706 706 result[failed] True ### 705 - (here) ### (here) - 711 711 if self._task.until: ### 706 - (here)报告头部给出产生该覆盖的 target 名以及指向目标目录与源码文件的链接——链接中的 commit 与覆盖率数据匹配确保看到的代码与 CI 实际测试的代码一致。弧标记的语义README 原文解释与 hacking/azp/incidental.py#L409-L428 的报告生成逻辑一致### (here) - 706写在第 705 行表示执行流从第 705 行进到第 706 行可以有多个出边行号### 706 - (here)写在第 711 行表示执行流从第 706 行进到第 711 行可以有多个入边行号(here)只是当前这一行的占位引用。弧arc信息仅对 Python 代码可用PowerShell 代码只报告被覆盖的行号。终端汇总行中也会区分统计口径exclusive 模式显示N arcs, M lines纯行号模式如 PowerShell只显示M lines报告头部相应地省略 arcs 计数见 hacking/azp/incidental.py#L391-L405。适用前提小结四个脚本全部面向 Ansible 官方 Azure Pipelinespipeline 20、dev.azure.com/ansible/ansible项目run.py还需要有效的AZP_TOKENincidental.py依赖ansible-test在$PATH中source hacking/env-setup、依赖一个完整的源码 git 仓库需要能git show被测 commit、以及包含run.json与各 jobcoverage-analyze-targets.json的下载结果目录报告中的 commit 链接以 CI 实际测试的 commit 为准与本地未推送的代码可能不一致因此分析前应先同步源码所有中间结果默认缓存在test/results/.tmp/incidental/{hash}/下可用--ignore-cache强制重新生成。【免费下载链接】ansibleAnsible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.项目地址: https://gitcode.com/GitHub_Trending/ans/ansible创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考