【免费下载链接】gsd-core
Git. Ship. Done - Core
导读
本文以 gsd-core 仓库中的 changeset 370-affected-tests-exclude-install-slow.md 为主体,讲解该仓库的受影响测试(affected-tests)选择器如何在 PR(Pull Request)场景下将install/slow两个"仅推送(push-only)"套件彻底排除在运行集合之外。读完本文,你将掌握 gsd-core 的套件命名约定与suiteOf检测逻辑、pickAffectedTests单一咽喉点的三层过滤原理、空选择回退到unit的设计,以及resolveRunPlan的完整运行决策表,并能够在本地复现和验证这一行为。
一、背景:gsd-core 的套件体系与受影响测试选择
gsd-core 将全部测试文件按文件名后缀标记划分为若干套件(suite),约定为:标记位于.test.cjs之前。例如foo.security.test.cjs属于security套件;foo.test.cjs(无标记)属于unit套件。完整的合法套件列表定义在 scripts/run-tests.cjs:
const SUITES = ['all', 'unit', 'integration', 'install', 'security', 'slow', 'qa'];其中带标记的套件由 scripts/lib/suite-detection.cjs 中的常量统一登记:
const MARKED_SUITES = ['integration', 'install', 'security', 'slow', 'qa'];对应的套件级 npm 脚本在 docs/TESTING-SUITES.md 中有完整说明:
npm test # everything(与旧行为一致) npm run test:unit # 仅 unit npm run test:integration # 仅 integration npm run test:install # 仅 install npm run test:security # 仅 security npm run test:slow # 仅 slow受影响测试(affected-tests)机制的目标是:给定一次变更的文件集合,通过require()/import依赖图计算出"可能被这次变更影响"的测试文件,只运行这些文件,从而在 PR 上获得远比全量测试快的反馈。该机制有两个入口:
- CI 侧:规则表驱动的 scripts/ci-test-scope.cjs(文档明确说明这是 CI 选测的权威映射);
- 本地/PR 侧:依赖图驱动的 scripts/run-affected-tests.cjs,它只是一个薄 CLI 入口,核心逻辑全部位于 scripts/affected-tests-lib.cjs。
二、问题:install 与 slow 套件为什么不能出现在 PR 运行中
install套件的测试(如release-tarball-smoke.install.test.cjs)是 npm 打包 + 全局安装级的长耗时集成测试,在 gsd-core 的 CI 编排中由独立的install-smoke.ymlworkflow负责运行;slow套件则是标记为低频率、长耗时的测试。它们被设计为push-only:只应在推送/发布级流水线中运行,而不是在每一次 PR 上重复执行——否则会显著拖长 PR 反馈周期,甚至触发按 chunk 的 Windows 超时(tests/ci-test-scope.test.cjs 中明确记录了这一历史教训)。
在修复(PR #370)之前,受影响测试选择存在三处漏洞,导致 PR 仍可能选中甚至运行这些 push-only 套件:
pickAffectedTests在 direct-change(直接变更的测试文件)、reverse-index(依赖图反查命中)、stem-match(文件名词干模糊匹配)三条选择路径上都可能把install/slow文件选入集合;- 当选择结果为空时,存在一个
DEFAULT_SMOKE_TESTS安装文件回退注入逻辑,会把 install 套件的 smoke 测试强行塞进 PR 运行; - 当变更触及关键路径(critical path)时,旧实现调用
runAllSuites——它会把包括install/slow在内的所有套件都运行一遍。
三、修复核心一:pickAffectedTests的单一咽喉点过滤
修复后的过滤逻辑集中在 scripts/affected-tests-lib.cjs,源码注释明确称之为"单一咽喉点(single chokepoint)":
// Drop any file whose suite is push-only. This is the single chokepoint — // it catches direct-change, reverse-index, AND stem-match selections. for (const file of selected) { const suite = suiteOf(path.basename(file)); if (PR_EXCLUDED_SUITES.has(suite)) selected.delete(file); }这段代码在pickAffectedTests完成三类选测(direct-change、reverse-index 反查、stem 启发式匹配)之后、返回之前统一执行:对集合中每个文件调用suiteOf判断其套件,凡命中PR_EXCLUDED_SUITES的立即从集合中删除。
PR_EXCLUDED_SUITES的定义在 scripts/affected-tests-lib.cjs:
// Suites that are push-only. PRs must never select or run these. const PR_EXCLUDED_SUITES = new Set(['install', 'slow']);把过滤收敛到"一个咽喉点"而非散落在三条选择路径里各自判断,带来的工程收益是单点可验证:只要保证咽喉点的删除逻辑正确,无论未来新增第几种选择路径,都不会再次出现 push-only 套件泄漏。
3.1 三类选择路径各自如何被覆盖
从 scripts/affected-tests-lib.cjs 的pickAffectedTests实现可以看到三条路径:
- direct-change:遍历变更文件,凡
tests/下且仍存在(未被删除)的.test.cjs直接加入集合(L300-L313); - reverse-index 反查:对非测试文件的变更,从传递性反查索引
reverseIndex中取出所有可能依赖它的测试(L307-L312); - stem-match:对每个变更文件取 basename 去扩展名的词干,与全部测试文件路径做不区分大小写的子串包含匹配(L315-L322)。
咽喉点过滤位于三者之后,因此一条路径都不漏。
四、修复核心二:移除DEFAULT_SMOKE_TESTS回退注入,空选择运行 unit
旧的空选择回退逻辑会在"没有匹配到任何测试"时无条件注入一份DEFAULT_SMOKE_TESTS(其中包含 install 套件文件),这等于绕过了过滤规则。修复将其整体移除,新的行为是:空选择时不再注入任何 install 文件,而是以unit套件作为 smoke 回退。
这一契约变化在 tests/affected-tests-lib.test.cjs 中有直接断言:
test('pickAffectedTests returns empty (no install smoke) when nothing maps', () => { const allTests = [ 'tests/release-tarball-smoke.install.test.cjs', 'tests/some-unit.test.cjs', ]; const selected = pickAffectedTests( ['docs/CONTRIBUTING.md'], allTests, new Map(), ); assert.ok( !selected.includes('tests/release-tarball-smoke.install.test.cjs'), 'install smoke test must not be injected as fallback', ); assert.deepEqual(selected, []); });对应的运行侧行为由resolveRunPlan落地(见下一节):当选择为空时返回{ mode: 'suite', suite: 'unit' },运行器随后以unit套件作为 smoke 兜底。CI 侧的同类修复(bug-408:移除无条件 smoke 列表注入、改为 unit 回退)在 tests/ci-test-scope.test.cjs 中有独立测试覆盖,可见"空选择 → unit 回退"是仓库两侧一致的策略。
五、修复核心三:critical-path 分支从runAllSuites改为PR_FULL_SUITES
第三个修复点是 critical-path 分支。旧实现调用runAllSuites会运行包括install/slow在内的全部套件;修复后替换为PR_FULL_SUITES常量(scripts/affected-tests-lib.cjs):
// Suites run on every PR cell when the critical-path fallback fires. const PR_FULL_SUITES = ['unit', 'integration', 'security'];PR_FULL_SUITES恰好覆盖了所有 PR 合法套件(unit + integration + security),同时天然排除 install/slow。这一点在resolveRunPlan的注释中有更进一步的说明(scripts/affected-tests-lib.cjs):
Invariant: when widenRequired is true the executed set is ALWAYS ⊇ selected, because PR_FULL_SUITES covers every PR-eligible suite (unit + integration + security), so every concrete match that pickAffectedTests put into
selectedbelongs to one of those suites and will be exercised by running all three.
也就是说,当出现 widen(宽化)回退时,运行三套 PR 套件是"严格包含"咽喉点过滤后所选集合的超集——不存在过滤掉的选择会通过 widen 路径被重新放行的空子。
5.1resolveRunPlan完整决策表
从 scripts/affected-tests-lib.cjs 的实现可以得到一张完整的运行决策表:
| 输入状态 | 决策结果 |
|---|---|
noChanges(相对 base 无变更) | { mode: 'suite', suite: 'unit' } |
criticalPath(关键路径变更) | { mode: 'suites', suites: PR_FULL_SUITES } |
widenRequired(孤儿源文件无静态依赖测试) | { mode: 'suites', suites: PR_FULL_SUITES } |
| 选择结果为空(widen=false) | { mode: 'suite', suite: 'unit' } |
| 有具体选择 | { mode: 'files', files: selected } |
六、suiteOf的导出机制:复用规范检测逻辑而非重复实现
changeset 中提到的"suiteOf从run-tests.cjs导出(由require.main守卫)"这一细节,在代码中有两层实现依据。
第一层:run-tests.cjs通过"仅当作为主入口运行时才执行 CLI 主流程"的守卫,使其既可被 require 复用、又不会在被导入时误触发命令执行(scripts/run-tests.cjs):
if (require.main === module) { runMain(main); } module.exports = { suiteOf, ensureBuiltArtifacts, // ... parseArgs、selectFiles、setupRunTempRoot 等 };第二层:suiteOf的实际实现已经进一步下沉到独立模块scripts/lib/suite-detection.cjs,run-tests.cjs只是原样再导出以保持向后兼容。该文件头部的注释解释了这一拆分的缘由(#4591 packaging fix):run-tests.cjs本身不随发布包分发(它是仓库开发期工具),如果已发布的脚本(如scripts/gen-platform-conformance-tier.cjs)直接 require 它,会在安装后遇到MODULE_NOT_FOUND(#2858);因此把suiteOf及其背后的MARKED_SUITES提取到可发布的scripts/lib/下,再经由run-tests.cjs导出。核心实现(scripts/lib/suite-detection.cjs):
function suiteOf(filename) { const name = basename(filename); if (!name.endsWith('.test.cjs')) return null; const base = name.slice(0, -'.test.cjs'.length); const lastDot = base.lastIndexOf('.'); if (lastDot === -1) return null; const marker = base.slice(lastDot + 1); return MARKED_SUITES.includes(marker) ? marker : null; }要点:无标记文件(如foo.test.cjs)属于unit套件,这里返回null;带标记文件(如foo.install.test.cjs)返回其标记字符串。affected-tests-lib.cjs在文件顶部以require('./run-tests.cjs')引入suiteOf(scripts/affected-tests-lib.cjs),从而与run-tests.cjs的套件过滤行为共享同一份规范实现,避免两处套件判定逻辑漂移。
七、测试验证:行为契约的落点
修复后的行为契约由以下测试固定,便于在仓库中直接复现验证:
- 直接变更 + 反查命中,install 套件被排除:tests/affected-tests-lib.test.cjs 断言普通
tests/install.test.cjs(无套件标记,属 unit)保留、tests/tarball.install.test.cjs(install 标记)被剔除; - 直接变更 install 测试文件本身也被排除:tests/affected-tests-lib.test.cjs;
- stem-match 拉入的 install/slow 被排除:tests/affected-tests-lib.test.cjs;
- 空选择不再注入 install smoke:tests/affected-tests-lib.test.cjs;
- 常量契约:
PR_EXCLUDED_SUITES含install、slow,PR_FULL_SUITES含unit/integration/security且不含二者:tests/affected-tests-lib.test.cjs; - CI 侧 smoke 列表移除:bug-408 用例断言命令类变更不再无条件注入
core.test.cjs、package-manifest.test.cjs,无规则命中时回退['unit']:tests/ci-test-scope.test.cjs。
八、本地验证与使用方式
- 运行受影响测试选择相关测试:
node --test tests/affected-tests-lib.test.cjs - 运行 CI 测试范围规则相关测试:
node --test tests/ci-test-scope.test.cjs - 本地触发受影响测试(local-only 便捷入口,CI 不使用它):
npm run test:affected该命令对应 scripts/run-affected-tests.cjs,实际执行
runAffectedTests:先以GSD_AFFECTED_BASE→GITHUB_BASE_REF→origin/main的顺序解析对比基准(scripts/affected-tests-lib.cjs),再走"关键路径 → 反查选测 → widen 兜底 → unit smoke"的完整决策链。
九、总结
PR #370 的修复在 gsd-core 的受影响测试机制上确立了一个清晰且可验证的边界:PR 运行集合中永远不允许出现install/slow套件。实现上通过三个相互独立又彼此衔接的手段保证——pickAffectedTests单一咽喉点过滤覆盖全部选择路径、DEFAULT_SMOKE_TESTS注入移除后以unit作为空选择回退、critical-path 分支改用PR_FULL_SUITES;而suiteOf的提取与再导出机制(require.main守卫 +scripts/lib/suite-detection.cjs)保证了套件判定逻辑的唯一来源与向后兼容。这套设计既缩短了 PR 反馈周期,也通过单点收敛和测试契约避免了同类问题在未来回归。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
Wick Editor开发者指南:贡献代码与扩展功能的终极教程
Wick Editor开发者指南:贡献代码与扩展功能的终极教程 想要为Wick Editor这个免费开源的多媒体创作工具贡献代码或扩展功能吗?本指南将为你提供完
游戏开发开发工具如何在 Docker 测试环境里运行 Huginn 测试套件(rspec 与无头 Chrome)?
如何在 Docker 测试环境里运行 Huginn 测试套件(rspec 与无头 Chrome)? 如果你正在为 Huginn 开发或修改 Agent,改完代码
后端工作流自动化任务调度Lwan测试套件:如何编写和运行集成测试
Lwan测试套件:如何编写和运行集成测试 Lwan是一个实验性的、可扩展的高性能HTTP服务器,其测试套件是确保代码质量和稳定性的关键组成部分。本文将详细介绍L
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考