ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 pmdtester 为 PMD 做回归测试:本地运行、CI 集成与差异报告解读

使用 pmdtester 为 PMD 做回归测试:本地运行、CI 集成与差异报告解读
  • 静态分析
  • 代码质量

【免费下载链接】pmd

An extensible multilanguage static code analyzer.

项目地址:https://gitcode.com/gh_mirrors/pm/pmd
点击查看免费下载

导读

Pmdtester(PMD Regression Tester)是 PMD 项目自带的回归测试工具:当开发者修复了一个 issue、新增或修改了一条规则后,它会用同一组真实开源项目作为测试集,对比“基线版本”与“改动版本”的扫描结果,找出新增的违规(new violations)与移除的违规(removed violations),从而在问题合入主分支之前就暴露可能引入的误报(false positive)与漏报(false negative)。本文从官方文档 pmdtester.md 出发,结合本仓库中实际的 CI 配置与回归测试脚本,讲解如何在本地运行 pmdtester、它如何基于基线(baseline)工作、在 GitHub Actions 中如何自动产出并回评差异报告,以及贡献者提交 PR 前后应当如何使用这些能力。

Pmdtester 是什么

按官方开发者文档的定义,Pmdtester 是一个回归测试工具,核心目标是确保:

  • 修复一个 issue 之后,不会给 PMD 引入新的问题或非预期行为;
  • 新编写的规则能够按预期工作,而不是产生大量意料之外的违规报告。

它通过“改动前”与“改动后”两次扫描的差异对比来实现上述目标。回归差异报告(regression difference report)会以评论的形式回贴到 Pull Request 上,供审阅者快速判断改动是否安全(文档中即以 PR #1265 的评论为例说明这一机制)。

从仓库历史记录看,pmdtester 最早是 2018 年 Google Summer of Code(GSoC)项目的成果:PR #1254 将 danger 与 pmdtester 集成进 Travis CI,PR #1250 实现了 baseline 的自动上传,PR #1290 创建了关于 pmdtester 的官方文档(参见 release_notes_old_pmd6.md)。此后 pmdtester 作为 Ruby gem 持续迭代,仓库 release notes 中记录了它从 1.5.5 → 1.6.0 → 1.6.1 → 1.6.2 → 1.7.0 的版本演进(参见 release_notes_old_pmd7.md)。

需要说明的是:官方文档撰写时的 CI 载体是 Travis CI;当前仓库已全面迁移到 GitHub Actions,回归测试由.github/workflows/build.yml中的regressiontesterjob 承担(详见下文“CI 集成”章节),但 pmdtester 本身的工作方式与命令接口没有改变。

在本地安装并运行 pmdtester

安装

pmdtester 以 Ruby gem 形式分发,安装命令为:

gem install pmdtester --pre

其中--pre表示安装预发布(prerelease)版本,便于在正式版发布前使用最新功能。安装前请确保本机具备 Ruby 与 RubyGems 环境;在 CI 场景下,仓库通过.ci/files/Gemfile声明依赖(gem 'pmdtester')并使用bundle install安装(参见 Gemfile)。

运行本地回归测试并生成差异报告

官方文档给出的核心命令如下:

pmdtester -r YOUR_LOCAL_PMD_GIT_REPO_ROOT_DIR -b main -p YOUR_DEVELOPMENT_BRANCH

各参数含义:

参数含义
-r本地 PMD Git 仓库根目录(--local-git-repo的简写),pmdtester 需要从这个仓库读取历史、切换分支执行对比
-b基线分支(base branch),通常为main
-p开发分支(patch branch),即包含你的改动的分支

命令执行完毕后,回归差异报告会输出到当前工作目录下的target/reports/diff目录:

YOUR_WORKING_DIR/target/reports/diff

这与 CI 中的产物路径完全一致(GitHub Actions 中该目录会被打包为名为pmd-regression-tester的 artifact 上传,见下文)。

本地运行的前提条件

  • 本地必须有一份完整的 PMD Git 仓库克隆,且包含足够的历史记录(pmdtester 需要计算 merge-base,确定改动涉及的文件集合;CI 中.ci/files/pmdtester_start.sh会为此额外git fetch25~150 条提交);
  • 需要能访问 baseline 下载地址(本地运行会从https://pmd-code.org/pmd-regression-tester/拉取基线数据,参见.ci/files/pmdtester.rb中的--baseline-download-url参数),或者按 pmdtester 自身文档配置本地基线。

工作原理:基线对比驱动的差异报告

pmdtester 的核心思想是基线(baseline)对比:

  1. 维护者在main分支(或每次发布时)对一组固定的测试项目执行一次完整的 PMD 扫描,把结果打包成基线文件;
  2. 当某个 PR 或开发分支提交改动时,pmdtester 用改动后的代码重新扫描同一组测试项目;
  3. 逐项目、逐文件、逐规则对比两次结果,生成 diff:+表示新增的违规,-表示被消除的违规;
  4. 审阅者通过 diff 中新增违规的数量与内容,判断改动是否引入误报;通过移除的违规确认修复是否真正生效。

基线从哪里来

本仓库中,基线由 CI 定期重建并上传:

  • 快照基线:.github/workflows/publish-snapshot.yml中的create-regression-tester-baselinejob 在每次main分支快照构建后,以main为 patch 分支执行 pmdtester 的single模式,把target/reports/main/打包为main-baseline.zip,上传到pmd-code.org的pmd-regression-tester目录(参见 publish-snapshot.yml)。这正是 PR 构建时默认下载的基线。
  • 发布基线:.github/workflows/publish-release.yml的create-regression-tester-baselinejob 在每次正式发布后,以pmd_releases_${PMD_VERSION}为名生成并上传pmd_releases_${PMD_VERSION}-baseline.zip,同时镜像一份到 SourceForge(参见 publish-release.yml)。

发布流程文档将“为回归测试器创建并上传新的基线”列为每次发布必须完成的最后一步之一(参见 releasing.md 及其中 “regression-tester: New release baseline is uploaded” 的发布检查清单)。

差异报告的内容

CI 场景下,报告目录中至少包含两个机器可读文件(由.ci/files/pmdtester.rb写入):

  • summary.txt:对 PR 审阅者友好的汇总文本,会被直接贴到 PR 评论中;
  • conclusion.txt:结论状态,可取success/failure/skipped等,用于驱动 GitHub check 的最终结论。

当本次改动没有任何受影响的规则时,pmdtester_start.sh会提前短路,写入summary.txt为 “No regression tested rules have been changed.”、conclusion.txt为skipped,跳过整个测试过程(参见 pmdtester_start.sh)。

CI 集成:GitHub Actions 中的 regressiontester 工作流

回归测试 job

.github/workflows/build.yml中的regressiontesterjob 是 PR 与 push 构建中的一环,其关键配置如下(参见 build.yml):

配置项取值 / 说明
触发条件pull_request或push事件;依赖compilejob 先完成
超时时间60 分钟
运行环境ubuntu-latest;安装 Java 11 与 21、Ruby 4
缓存按.ci/files/project-list.xml与.ci/files/Gemfile.lock的哈希建立缓存(regressiontester-*),复用测试项目依赖与vendor/bundle
输入产物复用compilejob 产出的dist-artifact(解压到pmd-dist/target),避免重复构建 PMD
执行入口.ci/files/pmdtester_start.sh
输出产物target/reports/diff打包为 artifactpmd-regression-tester

这里用了一个独立的构建缓存,因为它包含测试项目(如 Spring Framework)及其依赖,与普通构建缓存刻意隔离。

事件分支处理与 git 历史准备

.ci/files/pmdtester_start.sh(参见 pmdtester_start.sh)承担了 CI 侧的关键准备工作:

  • 根据GITHUB_EVENT_NAME区分事件类型:
    • pull_request:基线分支取GITHUB_BASE_REF(通常是main),改动分支取pull/{PR_NUMBER}/head;
    • push:推送到main时基线与改动分支都是main,并通过PMD_REGRESSION_TESTER_PUSH_BEFORE(来自 build.yml 注入的github.event.before)记录“推送前”的 commit,作为第二参考点;推送到其他分支时则以main为基线。
  • 仅支持main作为基线分支,否则直接报错退出;
  • 为确保能找到改动分支与基线的 merge-base,脚本先git fetch --depth=25,若尚未找到合并基点则继续加深至最多 150 条提交,然后把本地main分支指向 merge-base,供 pmdtester 计算改动文件集;
  • 最后导出PMD_CI_BRANCH=main(PR 场景下还导出PMD_CI_PULL_REQUEST_NUMBER),执行bundle exec ruby .ci/files/pmdtester.rb。

pmdtester.rb:真正的调用入口

.ci/files/pmdtester.rb(参见 pmdtester.rb)以 Ruby 方式调用 pmdtester 的 APIPmdTester::Runner.new(args).run,其构造的参数与本地命令一一对应:

参数值作用
--local-git-repo./pmd本地 PMD 仓库路径
--list-of-project./pmd/.ci/files/project-list.xml测试项目清单
--base-branchmain(由PMD_CI_BRANCH注入)基线分支
--patch-branchHEAD改动分支
--patch-config./pmd/.ci/files/all-regression-rules.xml参与回归的规则集
--modeonline在线模式:联网下载基线
--auto-gen-config-自动生成仅含受影响规则的配置文件
--keep-reports-保留各次运行的中间报告
--error-recovery-单个项目失败不中断整体运行
--baseline-download-urlhttps://pmd-code.org/pmd-regression-tester/基线下载地址
--threads机器 CPU 核数并行扫描测试项目

若运行异常,脚本会兜底写出failure结论与提示维护者的 summary,避免 CI 静默丢失信息(参见 pmdtester.rb)。

报告如何回到 PR:publish-pull-requests 工作流

回归报告产成后,还需要被“发布”回 PR 页面。.github/workflows/publish-pull-requests.yml(参见 publish-pull-requests.yml)在 “Build Pull Request” 成功后运行(仅限官方仓库pmd/pmd,且该 workflow 不检出 PR 代码、不参与构建,只处理产物,安全可控):

  1. 下载docs-artifact与pmd-regression-tester两个产物;
  2. 用 AWS CLI 将docs与regression目录同步到 S3 桶pmd-pull-requests,路径为pr-{PR_NUMBER}/{PR_SHA}/docs与pr-{PR_NUMBER}/{PR_SHA}/regression(通过 CloudFront 提供 HTTPS 访问,数据保留 60 天后自动清理;因为 URL 中含 PR 的 head SHA,更新 PR 会重新上传,避免缓存穿透);
  3. 通过 GitHub CLI 为该 PR 的 commit 添加 commit status 与 check run,结论取自conclusion.txt;
  4. 用sticky-pull-request-comment动作把summary.txt内容作为评论创建/更新到 PR 上,并附上完整报告的链接。

由于这些页面是短生命周期产物,站点根目录配置了robots.txt(Disallow: /)以阻止搜索引擎收录(参见 github_actions_workflows.md)。

回归测试的配置资产

测试项目清单:project-list.xml

.ci/files/project-list.xml(参见 project-list.xml)定义了回归测试使用的真实世界测试项目,涵盖多种语言:

项目语言固定版本 / tag
checkstyleJavacheckstyle-9.1
spring-frameworkJavav5.3.13
openjdk-11(src/java.base)Javajdk-11+28
java-regression-testsJavamain
declarative-lookup-rollup-summariesApex固定 commit
fflib-apex-commonApex固定 commit
apex-link(samples子目录)Apexv2.3.0
OracleDBUtilsPL/SQL固定 commit
alsa-firmwareC++v1.2.1

每个<project>节点可配置如下元素,理解它们有助于本地自定义自己的测试项目集:

  • type/connection/tag:项目来源(git)与固定的检出点,保证基线可复现;
  • src-subpath:只分析仓库的某个子目录;
  • exclude-pattern:排除特定路径(如生成代码、非编译资源),避免噪音;
  • build-command:克隆后如何构建项目并产出classpath.txt——以 spring-framework 为例,脚本会打几个 patch 以适配 Java 11 编译并生成createSquishClasspath任务来导出 classpath;
  • auxclasspath-command:为 PMD 的类型解析(type resolution)提供 classpath;
  • cpd-options:该项目参与 CPD(重复代码检测)时的语言、最小 token 数与内存上限等选项。

参与回归的规则集:all-regression-rules.xml

.ci/files/all-regression-rules.xml(参见 all-regression-rules.xml)按规则分类目录(bestpractices / codestyle / design / documentation / errorprone / multithreading / performance / security)引用了Apex、PL/SQL、Java的全部规则。回归测试只覆盖规则发生变化的语言与类别:pmdtester 会基于改动涉及的规则生成实际运行的子配置(--auto-gen-config),因此即使规则全集很大,单次回归也只需要针对受影响的规则执行。

Gemfile 依赖

.ci/files/Gemfile声明了 CI 运行 pmdtester 所需的 Ruby 依赖:pmdtester本体,以及兼容新版 Ruby 所需的logger、ostruct、base64三个默认 gem(参见 Gemfile)。Gemfile.lock固定版本,与 project-list.xml 共同构成回归测试的缓存键。

贡献者视角:提交 PR 前后该怎么做

按官方贡献指南,PR 会自动构建;构建成功后,PMD Regression Tester 会针对上述几个测试项目运行扫描并生成报告(参见 contributing.md):

  • 报告列出新增的违规(改动可能引入的误报)与移除的违规(修复确实消除的漏报/误报),帮助维护者避免意外引入 false positives 或 false negatives;
  • 报告会作为评论回贴到 PR,并额外以 commit status / check run 的形式标记结果(success/failure/skipped);
  • 如果改动不涉及任何受回归规则集覆盖的规则,测试会以skipped快速通过,报告内容为 “No regression tested rules have been changed.”。

对贡献者而言,最实用的自查路径是在本地提前运行:

gem install pmdtester --pre pmdtester -r YOUR_LOCAL_PMD_GIT_REPO_ROOT_DIR -b main -p YOUR_DEVELOPMENT_BRANCH

然后检查YOUR_WORKING_DIR/target/reports/diff下的差异报告,确认新增违规数量为 0(或均为预期行为)后再提交 PR,可显著减少维护者在评审中的来回沟通成本。

相关文档与深入阅读

以下仓库内文档与本主题强相关,可继续深入:

  • github_actions_workflows.md:GitHub Actions 各工作流的全貌,含regressiontesterjob 与 PR 结果发布的说明;
  • contributing.md:贡献指南中关于回归测试自动运行的部分;
  • releasing.md:发布流程中基线创建与上传的步骤及检查清单;
  • infrastructure.md:包括 pmdtester gem 发布相关的基础设施说明;
  • pmdtester.md:本文依据的官方开发者文档;
  • 仓库内的实际配置与脚本:build.yml、publish-pull-requests.yml、publish-snapshot.yml、publish-release.yml、pmdtester.rb、pmdtester_start.sh、project-list.xml、all-regression-rules.xml。

小结

Pmdtester 为 PMD 这类规则引擎项目提供了一条可复现、自动化的回归防线:它以固定的测试项目集和基线报告为参照,把“规则改动是否引入新违规”变成一份可机器读取、可回贴到 PR 的差异报告。本文既覆盖了官方文档给出的本地安装与运行命令(gem install pmdtester --pre、pmdtester -r ... -b main -p ...、报告目录target/reports/diff),也基于仓库源码剖析了基线生成、CI job、事件分支处理、产物发布回 PR 的完整链路。无论你是想为本地改动做一次快速回归,还是打算为项目扩展新的测试项目集,都可以从上述配置资产出发,把 pmdtester 跑起来。

  • 静态分析
  • 代码质量

【免费下载链接】pmd

An extensible multilanguage static code analyzer.

项目地址:https://gitcode.com/gh_mirrors/pm/pmd
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进