Test Selector 统一使用指导
一、功能概述
精准测试选择器 — 基于已有覆盖率数据,从 PR/变更中智能选择需要执行的测试用例。支持 行级 + 函数级 匹配(并行执行,合并去重),文件级 匹配作为兜底(重命名/删除文件场景)。
入口:
python -m test_selector --repo vllm_ascend --github-pr "vllm-project/vllm-ascend#12379"
python -m test_selector --repo torch_npu --github-pr "pytorch/pytorch#150000"
PR 源选择:三仓库均使用 GitHub(
--github-pr);torch_npu 检测上游仓库 pytorch/pytorch。
核心工作流(main 函数结构):
┌─────────────────────────────────────────────────────────────────┐
│ main() 函数结构 │
├─────────────────────────────────────────────────────────────────┤
│ 行 A: 下载 diff(github.py / gitcode.py,拼接后格式一致) │
├─────────────────────────────────────────────────────────────────┤
│ 动作1: 测试文件提取(pr_detector,规则由 adapter 提供) │
│ - get_test_files_from_pr_diff() → new_tests │
│ - get_deleted_test_files_from_pr() → deleted_tests │
├─────────────────────────────────────────────────────────────────┤
│ 动作2: 全量触发变更检测(adapter.has_full_suite_changes) │
│ - vllm_ascend: csrc/ 目录变更(非 .md)→ 全量测试 │
│ - sglang / torch_npu: 恒为 False(原生代码变更不解析进精准匹配) │
├─────────────────────────────────────────────────────────────────┤
│ 动作3: 产品代码变更检测(change_detector) │
│ - parse_pr_diff_file() → changed_files_with_lines, renames │
│ └─ select_tests()(行级/函数级并行 + 文件级兜底) │
│ ├─ normal_files → 精准匹配 │
│ └─ renames/deleted → 旧路径文件级匹配(禁行级/函数级) │
├─────────────────────────────────────────────────────────────────┤
│ 合并结果: │
│ - base = has_full_suite ? 全量 : 精准结果(含 rename 旧路径) │
│ - result = (base ∪ new_tests) - deleted_tests │
├─────────────────────────────────────────────────────────────────┤
│ 输出: BASE_DIR/recommended_pytest_paths.txt │
└─────────────────────────────────────────────────────────────────┘
关键决策逻辑:
- csrc 变更检测(仅 vllm_ascend):diff 含
csrc/目录变更(排除.md)→ 执行全量测试 - 精准匹配:无全量触发时,基于行/函数/文件级匹配选择测试
- 文件重命名检测:rename 新路径在覆盖率数据中不存在,用旧路径做文件级匹配召回
- 新增/删除处理:新增测试文件直接加入;已删除测试文件从结果中移除
- 排除变更场景:纯注释/docstring、纯类型注解、首次新增的变量绑定、函数/类定义间空行插入、新增 def/class 整体、新文件等不构成代码变更的场景,在 diff 解析阶段剔除(见第五章)
代码结构:
test_selector/
├── cli.py # 统一 CLI(三仓共用 main 入口)
├── __main__.py # python -m test_selector 入口
├── github.py # PR 拉取(公共)
├── pr_detector.py # 检测 PR 内容:测试文件/删除文件/全量触发(规则委托 adapter)
├── diff_parser.py # 排除变更场景:纯注释/docstring/纯注解/首次绑定/新文件跳过等
├── noise_filter.py # map 噪音处理:import/def/class/docstring/空行
├── function_parser.py # 函数区间解析(sglang 优化版,区间线性扫描)
├── coverage_selector.py # 构建 测试用例→覆盖文件/行号 映射
├── change_detector.py # 哈希比对 + PR diff 解析
├── test_selector.py # TestSelector:行/函数/文件三级级联匹配
└── repos/ # 仓库差异按仓归类
├── base.py # RepoAdapter 抽象接口
├── vllm_ascend.py # vllm_ascend 适配器
├── sglang.py # sglang 适配器
├── torch_npu.py # torch_npu(上游 PyTorch / GitHub)适配器
└── __init__.py # 适配器注册表 get_adapter()
二、目录结构要求
统一代码库位于仓库根目录(test_selector 包的上级目录),所有相对路径均基于此解析(绝对路径保持原样)。
仓库根目录/
├── test_selector/ # 统一代码库
├── vllm/test_selector.py # vllm 薄入口
├── sglang/test_selector.py # sglang 薄入口
├── PyTorch/test_selector.py # PyTorch 薄入口
│
├── <覆盖率数据目录>/ # 覆盖率数据(仅构建 map 时需要,名称不写死)
│ ├── <vllm 布局>/
│ │ ├── tests__e2e__...__test_xxx/
│ │ │ └── covdata/ # vllm:覆盖率文件在 covdata/ 子目录
│ │ │ └── coverage.*
│ │ └── cpu-ut/
│ │ └── covdata/
│ │ └── coverage.*
│ ├── <sglang 布局>/
│ │ └── ____w__sglang__sglang__test__registered__npu__...__test_xxx/
│ │ └── coverage.linux-* # sglang:覆盖率文件直接放在测试目录下
│ └── <pytorch 布局>/
│ └── _inductor__test_add/ # pytorch:目录名含 __ 或以 test_ 开头
│ └── covdata/ # pytorch:覆盖率文件在 covdata/ 子目录
│ └── coverage.*
│
├── covstub/ # 源码目录(函数级匹配需要)
│ ├── vllm_ascend/ # vllm:对应仓库根目录
│ ├── sglang/ # sglang:对应仓库 python/sglang/
│ └── torch/ # torch_npu:对应 pytorch/pytorch 仓库根目录
├── test_case_map.json # 自动生成的映射文件
└── recommended_pytest_paths.txt # 推荐测试用例列表(输出)
覆盖率目录布局自动探测(三仓库兼容):
- 测试目录下存在
covdata/子目录(vllm / pytorch 布局)→ 从covdata/读取coverage.* - 无
covdata/子目录(sglang 布局)→ 直接读取测试目录下的coverage.*
测试用例目录识别规则(adapter 提供):
| 仓库 | 规则 |
|---|---|
| vllm_ascend | 目录名以 tests__ 开头,或等于 cpu-ut |
| sglang | 目录名以 ____w__sglang__sglang__test__ 开头 |
| torch_npu | 目录名含 __,或以 test_ 开头(如 _inductor__test_add) |
测试用例名称转换规则(adapter 提供):
| 仓库 | 转换示例 |
|---|---|
| vllm_ascend | tests__e2e__pull_request__one_card__test_xxx → tests/e2e/pull_request/one_card/test_xxx.py;含 --test_foo → ...::test_foo(函数级);cpu-ut 保持不变 |
| sglang | ____w__sglang__sglang__test__registered__npu__basic_function__backends__test_npu_sampling_backend → test/registered/npu/basic_function/backends/test_npu_sampling_backend.py;含 -- → ::(函数级) |
| torch_npu | _inductor__test_add → _inductor/test_add.py;含 --test_foo → ...::test_foo(函数级) |
产品代码路径前缀(diff / 覆盖率库路径 → 仓库相对路径):
| 仓库 | 前缀 | 相对路径示例 |
|---|---|---|
| vllm_ascend | vllm_ascend/ |
vllm_ascend/core/worker.py → core/worker.py |
| sglang | python/sglang/ |
python/sglang/srt/models/qwen3_vl.py → srt/models/qwen3_vl.py |
| torch_npu | torch/ |
torch/distributed/utils.py → distributed/utils.py |
三、基础用法
相对路径基于仓库根目录解析,也支持绝对路径。
--source-dir建议传入;--coverage-dir仅构建 map 时需要(--build-map或 map 文件不存在时必填),推荐测试时无需传入。
1. 构建测试用例映射(首次使用或数据更新后)
# vllm_ascend
python -m test_selector --repo vllm_ascend --build-map \
--coverage-dir "VLLM-ASCEND@task_2026072021" \
--source-dir ./covstub
# sglang
python -m test_selector --repo sglang --build-map \
--coverage-dir "sglang@20260908" \
--source-dir ./covstub
# torch_npu
python -m test_selector --repo torch_npu --build-map \
--coverage-dir "PyTorch@Task20260903" \
--source-dir ./covstub
输出示例:
=== Building Test Case Mapping ===
Scanning test cases...
Found 32 test cases
[1/32] Processing ____w__sglang__sglang__test__registered__npu__basic_function__backends__test_npu_sampling_backend...
-> 450 files, 8939 lines
...
Test case mapping saved to: test_case_map.json
2. 从 PR 推荐测试
# vllm_ascend(GitHub)
python -m test_selector --repo vllm_ascend \
--github-pr "vllm-project/vllm-ascend#12379" \
--source-dir ./covstub
# sglang(GitHub)
python -m test_selector --repo sglang \
--github-pr "sgl-project/sglang#37043" \
--source-dir ./covstub
# torch_npu(GitHub 上游 pytorch/pytorch)
python -m test_selector --repo torch_npu \
--github-pr "pytorch/pytorch#150000" \
--source-dir ./covstub
推荐测试时加载已存在的
test_case_map.json,不需要--coverage-dir。只有 map 文件不存在(首次使用)或需重建时才传入。
四、核心参数说明
参数默认值由所选仓库适配器(cli_defaults())提供;三仓库当前默认值一致,后续可按仓调整。
公共参数
| 参数 | 必填 | 说明 |
|---|---|---|
--repo / -r |
否 | 仓库适配器:vllm_ascend / sglang / torch_npu(默认:vllm_ascend,薄入口默认各自仓库) |
--github-pr / -pr |
二选一 | GitHub PR(三仓库通用),格式:owner/repo#pr_number 或仅 pr_number(自动从 git remote 推导);torch_npu 传上游 pytorch/pytorch#N |
--gitcode-pr |
二选一 | GitCode PR(公共备用模块,当前无仓库默认使用),格式:owner/repo#pr_number 或仅 pr_number(自动从 git remote 推导);两参数互斥 |
--source-dir / -s |
建议 | 源码目录(默认:covstub;函数级匹配与噪音过滤需要) |
--map-file / -m |
否 | 映射文件(默认:test_case_map.json) |
--coverage-dir / -c |
构建 map 时 | 覆盖率数据目录(默认:coverage) |
--build-map / -b |
否 | 强制重建测试用例映射;仅构建 map 时使用 |
--min-affected / -a |
否 | 最少受影响行数阈值(默认:1) |
--dedup |
否 | 去重:相同覆盖行的测试只保留一个(默认关闭) |
--skip-imports |
否 | 函数级匹配时跳过 import 语句行(默认关闭) |
匹配粒度开关
| 参数 | 默认 | 说明 |
|---|---|---|
--enable-line-match |
开启 | 启用行级匹配(默认值来自 adapter) |
--disable-line-match |
- | 关闭行级匹配(优先级高于 enable) |
--enable-function-match |
开启 | 启用函数级匹配(默认值来自 adapter) |
--disable-function-match |
- | 关闭函数级匹配(优先级高于 enable) |
处理规则:disable 参数优先于 enable:
if args.disable_line_match:
args.enable_line_match = False
if args.disable_function_match:
args.enable_function_match = False
提示:文件重命名/删除场景内部已固定使用"仅文件级匹配"(行级/函数级强制关闭),不受这些开关影响。
仓库差异对照
| 维度 | vllm_ascend | sglang | torch_npu |
|---|---|---|---|
| PR 源 | GitHub | GitHub | GitHub(pytorch/pytorch) |
| 产品代码前缀 | vllm_ascend/ |
python/sglang/ |
torch/ |
| 测试目录识别 | tests__ 前缀 / cpu-ut |
____w__sglang__sglang__test__ 前缀 |
目录名含 __ 或以 test_ 开头 |
| 覆盖率文件位置 | covdata/ 子目录 |
测试目录下直接放置(兼容探测 covdata) | covdata/ 子目录 |
| 测试文件规则 | tests/e2e/pull_request/、tests/ut/ 下 test_*.py |
test/registered/、test/{unit,e2e,integration}/ 下 test_*.py |
test/ 下 test_*.py |
| 全量触发变更 | csrc/ 目录(非 .md)→ 全量测试 | 无(csrc/rust 走精准匹配) | 无(原生变更走精准匹配) |
| 测试名规范化 | --→::、__→/、文件级补 .py |
剥离编码前缀恢复 test/、__→/、--→:: |
--→::、__→/、文件级补 .py |
| 变更检测 | 本地哈希比对(默认)或 PR diff | 同左 | 同左 |
五、匹配逻辑详解
全量触发变更检测(仅 vllm_ascend)
当 diff 中检测到 csrc/ 目录变更(排除 .md 文件)时,直接执行全量测试,跳过精准匹配:
检测到 csrc 目录变更
→ base = 全量测试用例(test_case_map 中所有条目)
→ 跳过 parse_pr_diff_file() 和 select_tests()
→ 直接进入合并步骤
原因:csrc 目录通常包含底层 C/C++ 代码,其变更可能影响多个模块,难以通过精准匹配覆盖。
sglang 不触发全量:
csrc/*.cu、rust/*.rs等原生代码变更不会被解析进精准匹配(仅保留.py),推荐结果由 Python 产品代码匹配 + 新增/删除测试文件决定。torch_npu 不触发全量:
torch/csrc/等原生代码(C++/CUDA)变更不会被解析进精准匹配(仅保留.py)。注意:纯原生代码变更的 PR 可能返回 0 推荐,此时存在漏测风险,建议人工评估是否需跑相关用例(详见故障排除表)。
排除变更场景(diff_parser)
_parse_diff_base_lines() 解析统一 diff 文本为受影响的 base(变更前)行号,并在解析阶段/后续分类阶段排除以下不构成代码变更的场景:
| 场景 | 处理 |
|---|---|
| 纯注释/docstring 变更 | 删除组全为注释/docstring 且新增为注释/doc 说明 → 剔除 |
| 纯类型注解变更(插入/替换/删除) | 可证明惰性的注解(函数体内、普通类、模块级,无副作用)→ 豁免 |
| 首次新增的变量绑定(纯插入) | 全部为绑定全新名字的带值赋值(名字在 base 中不存在、RHS 惰性、无尾逗号行)→ 豁免 |
| 函数/类定义之间的空行插入 | 位于两个 def/class 之间 → 排除 |
| 新增 def/class 整体 | 插入文本属于新定义的函数/类 → 排除 |
新文件(--- /dev/null + @@ -0,0 ...) |
无 base 版本,跳过行级解析(避免无意义的 base 内容拉取) |
| 函数体内纯插入 | 记录插入位置上方的行号 |
| 隔离空行删除 | 按单行插入处理为候选对(上、下两行) |
候选对分类(_classify_candidate_pairs,需要 base 文件内容):通过 GitHub contents API / GitCode raw 接口单路拉取 base 内容后做 AST 分类;拉取失败重试 3 次后直接退出(不允许降级),仅 AST 解析失败等无法分类的场景回退为 hunk 范围内双侧计数。
新增测试文件处理
从 diff 中提取新增/修改的测试文件(规则由 adapter 提供),直接加入推荐列表:
new_tests = pr_detector.get_test_files_from_pr_diff(diff_content, adapter)
# vllm: 匹配 tests/e2e/pull_request/... 或 tests/ut/... 下 test_*.py
# sglang: 匹配 test/registered/... 或 test/{unit,e2e,integration}/... 下 test_*.py
# torch_npu: 匹配 test/ 下 test_*.py
已删除测试文件处理
从 diff 中检测被删除的测试文件(--- a/... + +++ /dev/null 对),从推荐列表中移除:
deleted_tests = pr_detector.get_deleted_test_files_from_pr(diff_content, adapter)
结果合并公式
result = (base ∪ new_tests) - deleted_tests
其中:
base = has_full_suite ? 全量测试 : 精准匹配结果
文件重命名/删除检测
当 diff 中检测到文件重命名或删除时,采用特殊处理策略:
检测到 rename: old_path -> new_path
→ new_path 在覆盖率数据中不存在(改名后未重新跑测试),精准匹配无效
→ old_path 在覆盖率数据中仍存在,单独做文件级匹配
→ 结果合并去重
处理流程:
# 1. 检测 rename / delete(detect_renames,仅产品代码前缀下)
renames = detect_renames(diff_content) # {old_path: new_path}
deleted_files = ... # [path, ...]
# 2. rename 新路径 / 删除文件从 changed_files 中排除(行级解析阶段已排除)
# 3. old_path 做文件级匹配
for path, label in file_level_paths: # rename 旧路径 + 删除路径
fl_selected, _ = select_tests(
{path: set()},
enable_line_match=False, # 禁用(可能没有实际代码变更)
enable_function_match=False, # 禁用
enable_file_match=True, # 仅文件级(召回覆盖旧文件的测试)
)
selected.extend(fl_selected)
控制台输出示例:
=== Detected 1 Product Code Renamed File(s) - Using File-Level Matching ===
vllm_ascend/core/worker.py -> vllm_ascend/core/worker_v2.py
=== File-Level Matched Tests for vllm_ascend/core/worker.py -> vllm_ascend/core/worker_v2.py ===
cpu-ut
tests/e2e/pull_request/one_card/test_worker.py
行级匹配
精确计算变更行与测试覆盖行的交集:
变更行: {100, 101, 102, 150, 151}
测试覆盖: {100, 101, 200, 201}
→ 交集: {100, 101} → 相关
函数级匹配
如果变更行落在某个函数内,则该函数被覆盖的所有测试都相关:
变更行 150 位于函数 process_request()
函数 process_request() 覆盖行: {140-200}
测试A 覆盖: {145, 146, 147} → 相关(覆盖了 process_request)
合并去重
行级和函数级同时执行,结果合并去重(行级结果优先);无结果时级联文件级匹配兜底。
变更检测方式(change_detector)
- PR 模式:
parse_pr_diff_file()解析 PR diff,产出变更行号 + 重命名映射 + 删除列表 - 本地模式(默认):
detect_changes_by_comparison()扫描--source-dir下全部.py,与.file_hashes.json基线比对 MD5,变更文件保守返回全部行号(1~9999),首次运行生成基线
GitCode PR 拉取(公共备用模块)
GitCode PR 拉取通过公共模块 test_selector.gitcode(--gitcode-pr,当前无仓库默认使用;torch_npu 已切换到 GitHub 上游仓库 pytorch/pytorch),与 GitHub 版(test_selector.github,--github-pr)的差异:
| 维度 | github.py | gitcode.py |
|---|---|---|
| PR 详情/diff 获取 | /pulls/{n} 返回 base.sha;diff 直连 github.com/{repo}/pull/{n}.diff 公开端点 |
/pulls/{n} 返回 base.sha;diff 优先 .diff 公开端点,失败兜底 /pulls/{n}/files 拼接 |
| diff 拼接 | 无需拼接 | _build_unified_diff() 合成 git 标记(rename from/to、deleted file mode、--- a/ / +++ b/ 头),结果格式与 GitHub 一致;patch.diff 为空时:纯改名/删除/新增仍合成标记(保文件级信号)、二进制/纯 mode 变更显式跳过、too_large 打印警告且行级变更不包含在拼接结果中 |
| base 内容获取 | GitHub contents API(?ref=base_sha,base64) |
raw.gitcode.com/{repo}/raw/{sha}/{path} |
| 认证方式 | Authorization: Bearer(GITHUB_TOKEN / GH_TOKEN) |
access_token 查询参数(GITCODE_TOKEN) |
拼接后的 diff 格式与 GitHub 一致,因此下游
diff_parser的噪音分类逻辑完全复用,不感知数据源差异。 代理要求:GitCode/GitHub API 均需直连外网;华为内网环境必须配置代理(见第九章)。若未配置代理,urllib抛WinError 10060连接超时,3 次重试后退出。 不允许降级:PR 详情(base.sha)、diff 主路(.diff端点)与 base 文件内容拉取失败均重试 3 次(间隔 30 秒,避开边缘限流窗口)后直接退出,不降级继续;diff 获取仅 GitCode 保留/files拼接兜底(GitHub 无兜底),兜底同样重试 3 次,全部失败即退出。
六、Map 构建逻辑
噪音过滤功能
构建 Map 时,自动过滤无效噪音行(NoiseFilter,每文件一次 AST 解析并缓存):
| 类型 | 说明 | 示例 |
|---|---|---|
import |
import/from...import 语句行(含多行 import) | import torch、from X import Y |
def |
函数定义行(含多行 def 头部) | def func():、async def foo(): |
class |
类定义行 | class MyClass: |
docstring |
模块级、类、函数的 docstring 行 | """doc"""、'''doc''' |
@staticmethod |
静态方法装饰器 | @staticmethod |
@classmethod |
类方法装饰器 | @classmethod |
@property |
属性装饰器 | @property |
blank |
空行/纯空白行 | 空行、仅含空格/制表符的行 |
装饰器过滤规则:只过滤 @staticmethod、@classmethod、@property 三种;其他装饰器(如 @triton.jit、@wraps、@contextmanager)保留。
空行过滤说明:Coverage arc 数据基于字节码控制流转移,空行也会作为控制流节点被记录(如 if 块结束后的空行、return 后的空行)。不过滤会导致行级匹配命中空行,产生误报。
生效条件:--source-dir 已指定(用于定位源码文件进行 AST 解析)。
空文件自动过滤:覆盖行经噪音过滤后为空则该文件不写入 map(减少 map 体积,不影响选择逻辑)。
Map 文件结构
{
"test/registered/npu/basic_function/backends/test_npu_sampling_backend.py": {
"files": {
"srt/arg_groups/model_overrides/qwen3_vl.py": [16, 19],
"srt/configs/qwen3_vl.py": [5, 6, 143, 144]
},
"file_count": 2,
"line_count": 4
},
"tests/ut/core/test_distributed.py::test_get_global_rank": {
"files": {
"vllm_ascend/distributed/utils.py": [50, 51, 52, 60, 61]
},
"file_count": 1,
"line_count": 5
}
}
注意:
- map 的 key 为 normalize 后的测试路径(vllm:
tests/.../test_xxx.py或...::test_func、cpu-ut;sglang:test/.../test_xxx.py;torch_npu:test/.../test_xxx.py、_inductor/test_add.py等,文件级补.py) - files 的 key 为剥离产品代码前缀后的相对路径(vllm:
core/worker.py;sglang:srt/xxx.py;torch_npu:distributed/xxx.py) - 保存时强制使用 LF 换行(
newline="\n"),避免 Windows CRLF 导致 JSON 体积膨胀,保证跨平台 git 差异对比一致
七、输出说明
控制台输出示例
场景1:检测到 csrc 变更(vllm,执行全量)
=== CSRC Directory Changes Detected - Running Full Test Suite ===
=== Full Test Suite: 3481 tests ===
=== Recommended Test Cases (3483 tests) ===
Results saved to: recommended_pytest_paths.txt
场景2:精准匹配
Parsed 4 changed files:
vllm_ascend/core/worker.py: 2-5
vllm_ascend/distributed/utils.py: 1339-1340
=== Selecting Affected Test Cases ===
Line match: 12 tests, Function match: 3 tests, Total: 15 tests
======================================================================
Code changes: 4 files, 15 lines
Selected: 15 test cases (Line+Function match)
======================================================================
# Test Case Affected Lines
----------------------------------------------------------------------
1 cpu-ut 3 (2-5)
2 tests/e2e/pull_request/four_card/context_parallel/test_accuracy 3 (114, 3987-3988)
...
场景3:包含新增/删除测试
=== New Test Files Added: 1 ===
['tests/ut/core/test_new_feature.py']
=== Deleted Test Files Removed: 1 ===
['tests/ut/core/test_deprecated.py']
输出文件(recommended_pytest_paths.txt)
写入仓库根目录,每行一个测试用例路径:
cpu-ut
tests/e2e/pull_request/four_card/context_parallel/test_accuracy
test/registered/npu/basic_function/backends/test_npu_sampling_backend.py
...
八、典型使用场景
场景1:分析 PR
# GitHub PR(vllm_ascend / sglang)
python -m test_selector --repo vllm_ascend \
--github-pr "vllm-project/vllm-ascend#16104" -s ./covstub
python -m test_selector --repo sglang \
--github-pr "sgl-project/sglang#37043" -s ./covstub
# GitHub PR(torch_npu,上游 pytorch/pytorch)
python -m test_selector --repo torch_npu \
--github-pr "pytorch/pytorch#150000" -s ./covstub
场景2:更新覆盖率数据后重建映射
# 1. 跑完测试,得到新的覆盖率数据
# 2. 重建映射
python -m test_selector --repo sglang --build-map -c "sglang@20260909" -s ./covstub
# 3. 推荐测试
python -m test_selector --repo sglang --github-pr "sgl-project/sglang#37758" -s ./covstub
场景3:精准回归测试
# 推荐测试
python -m test_selector --repo vllm_ascend --github-pr "owner/repo#123" -s ./covstub
# 查看 recommended_pytest_paths.txt 获取测试列表并执行
pytest tests/e2e/pull_request/one_card/test_worker.py -v
场景4:本地哈希比对检测变更(无 PR)
# 首次运行生成基线 .file_hashes.json
python -m test_selector --repo vllm_ascend -s ./covstub
# 修改源码后再运行 → 检测到变更文件并推荐
python -m test_selector --repo vllm_ascend -s ./covstub
场景5:代码库内部结构说明
from test_selector.repos import get_adapter
from test_selector.coverage_selector import CoverageSelector
from test_selector.change_detector import CodeChangeDetector
from test_selector.test_selector import TestSelector
adapter = get_adapter("sglang") # 选择仓库适配器(vllm_ascend / sglang / torch_npu)
selector = CoverageSelector("coverage", "covstub", adapter)
selector.build_test_case_map()
selector.save_map("test_case_map.json")
detector = CodeChangeDetector("covstub", adapter)
changed = detector.detect_changes_by_comparison()
ts = TestSelector(selector.test_case_map, adapter)
selected, reason = ts.select_tests(changed, source_dir="covstub")
九、故障排除
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
Error: --coverage-dir is required when building the test case map |
map 文件不存在且未传覆盖率目录 | 首次使用需传 --coverage-dir(或先 --build-map) |
Coverage data directory not found |
覆盖率目录路径不对 | 确认 --coverage-dir 指向含测试用例目录的根目录 |
没有测试用例覆盖变更的代码行 |
变更文件无覆盖数据 | 确认覆盖率数据来自变更前的全量用例 |
解析 PR 失败 / GitHub API 超时 |
内网无法直连 GitHub | 设置代理环境变量后重试(见下方) |
WinError 10060 连接超时(GitCode) |
内网无法直连 GitCode API | 设置代理环境变量后重试(见下方);git 全局已配置 proxycn2.huawei.com:8080 认证代理时,PowerShell 中执行 $env:HTTPS_PROXY=... 后重试 |
函数级匹配失败 |
源码目录路径不对 | 确认 --source-dir 指向包含 vllm_ascend/ / sglang/ / torch/ 包的根目录 |
| 推荐结果过多 | 变更函数被大量测试覆盖 | 启用 --dedup 或提高 --min-affected |
| 推荐结果为空 | PR 仅含原生代码变更且无新测试文件(sglang) | 正常,原生代码变更不触发全量 |
| 推荐结果为空 | PR 仅含原生代码变更(torch_npu,如 torch/csrc/ 下 C++/CUDA 文件) |
正常但存在漏测风险:原生代码变更不解析进精准匹配;建议人工评估是否需跑相关用例 |
Full-Suite Changes Detected(vllm) |
diff 含 csrc/ 目录变更 | 正常行为,触发全量测试 |
New Test Files Added: X |
diff 中有新增测试文件 | 正常,直接加入推荐列表 |
Deleted Test Files Removed: X |
diff 中有已删除测试文件 | 正常,从推荐列表移除 |
Detected X Renamed File(s) |
diff 中包含文件重命名 | 正常,旧路径做文件级匹配 |
| 变更行数偏多、含 context 行 | diff 解析按 hunk 范围推算 | 已知限制,算法保守偏多报 |
Unknown repo 'xxx' |
--repo 拼写错误 |
使用 vllm_ascend / sglang / torch_npu |
代理配置示例(华为内网环境):
# Windows PowerShell / CMD
set HTTP_PROXY=http://<user>:<pwd>@proxycn2.huawei.com:8080/
set HTTPS_PROXY=http://<user>:<pwd>@proxycn2.huawei.com:8080/
python -m test_selector --repo vllm_ascend --github-pr "vllm-project/vllm-ascend#16104" -s ./covstub
python -m test_selector --repo torch_npu --github-pr "pytorch/pytorch#150000" -s ./covstub
说明:
- Python
urllib会自动读取HTTP_PROXY/HTTPS_PROXY环境变量走代理;脚本内部已通过自定义ssl_context关闭证书校验。- GitHub 与 GitCode 均适用;本机 git 全局代理(
git config --global http.proxy,如http://<user>:<pwd>@proxycn2.huawei.com:8080/)可直接复用为环境变量。- PowerShell 中设置环境变量用
$env:HTTPS_PROXY='...'(set仅对 CMD 有效)。