首页
/ Understand-Anything 大型仓库基准测试的“不支持结果”设计:区分不支持的分析能力与真实执行失败

Understand-Anything 大型仓库基准测试的“不支持结果”设计:区分不支持的分析能力与真实执行失败

2026-09-04 14:15:26作者:翟江哲Frasier

本文围绕 Understand-Anything(下称 UA)仓库中一份基准测试改进实施计划展开:让大仓库基准测试(large-repo benchmark)能够把“解析器不支持该文件”“调用图能力缺失”这类能力性不支持,与“已选定能力后真正执行失败”区分开。读完本文,你将理解该方案如何通过复用 filesSkipped 与 call-graph skipped 计数、在不改动报告 Schema 1.0.0 的前提下实现“降级但不失败”的语义,并能结合源码定位到能力检测、结果映射、批次汇总与状态判定的完整调用链。

一、背景与目标:基准测试的“假失败”问题

UA 的 /understand 流程依赖一套确定性的辅助脚本(扫描、导入图、批次规划、Tree-sitter 结构提取)。当基准测试在超大、多语言 monorepo 上运行时,难免遇到注册表中没有对应解析器的文件(例如某些私有格式或脚本变体),或某个解析器只支持结构提取、不支持调用图提取。

问题在于:如果把“没有插件”“没有该能力”一律按失败处理,基准报告的状态就会被错误地拉低为 failed,使规模证据失去可比性;反之,如果把“解析器已声明能力却抛异常/返回非法值”也吞掉,则会掩盖真实的一致性(integrity)问题。

实施计划文档给出了明确的目标与架构决策:

Goal: Make the large-repository benchmark distinguish unsupported or unavailable analysis capabilities from real execution failures.

Architecture: Reuse the existing filesSkipped and call-graph skipped paths rather than changing report schema 1.0.0. Capability detection happens before interpreting parser results; only a selected, advertised capability that fails remains fatal.

翻译成设计原则就是:

  1. 能力检测先于结果解释——先看“这个文件有没有插件、插件声明了什么能力”,再决定如何解释后续结果;
  2. 不升级报告 Schema——复用现有 filesSkipped(结构侧)和 call-graph skipped 计数,保持 JSON/Markdown 双报告与 large-repo-report-1.0.0.schema.json 的兼容性;
  3. 只有一种情况保持致命——能力被选中并声明(advertised)之后,再抛异常或返回非法值,仍是 failed

技术栈为:Node.js ESM、PluginRegistry、Vitest、JSON Schema 2020-12、pnpm。

二、全局约束(Global Constraints)

计划文档列出了六条硬性约束,它们是后续所有实现步骤的验收边界:

约束 说明
保持 Schema 不变 large-repo-report-1.0.0.schema.json 与 JSON/Markdown 成对报告兼容性不被破坏
不支持文件可入账 结构分析不支持的文件必须被计入,产生 degraded 而非 failed,且退出码为 0
调用图能力缺失是跳过 可选的 call-graph 能力缺失时按 skipped 处理,不算失败
选中后的异常仍失败 能力被选中后的异常或非法值必须保持 failed
不削弱完整性检查 路径、digest、隐私(脱敏)、清理、非法输出检查均不得放松
输出不落盘于被测仓库 基准产物不得写入被测仓库内部

这些约束在 docs/benchmarks/large-monorepo.md 中已有对应的公开语义描述(后文第四节会给出原文对照)。

三、源码级实现:能力感知的结果映射

3.1 核心入口 analyzeFileWithOutcomes()

结果映射逻辑被抽离为纯函数模块 extract-structure-result.mjs,与带 shebang 的 CLI 入口分离,保证单元测试不必导入可执行脚本。核心函数签名:

export function analyzeFileWithOutcomes(registry, file, content)

其返回值同时携带“数据”与“结果分类”(outcome):

{ analysis, callGraph, structureOutcome, callGraphOutcome }
// structureOutcome / callGraphOutcome ∈ 'succeeded' | 'failed' | 'skipped'

第一步:按扩展名/路径选择插件,插件不存在即双跳过。extract-structure-result.mjs#L132-L146

const wantsCallGraph =
  file.fileCategory === 'code' || file.fileCategory === 'script';
const selectedPlugin = typeof registry.getPluginForFile === 'function'
  ? registry.getPluginForFile(file.path)
  : registry;

if (selectedPlugin === null) {
  return {
    analysis: null,
    callGraph: null,
    structureOutcome: 'skipped',
    callGraphOutcome: 'skipped',
  };
}

这里的能力探测依赖 PluginRegistry.getPluginForFile(),它在注册表中按文件路径查找解析器,找不到时返回 null——这正是“已知不支持”的信号,与“找到了插件但调用炸了”严格区分开。

第二步:读取插件声明的能力。extract-structure-result.mjs#L148-L151

const supportsFullAnalysis =
  typeof selectedPlugin?.analyzeFileFull === 'function';
const supportsSeparateCallGraph =
  typeof selectedPlugin?.extractCallGraph === 'function';

解析器有两种形态:

  • 组合式:提供 analyzeFileFull,一次返回 { structure, callGraph }
  • 分离式:只提供 analyzeFile(结构)和/或 extractCallGraph(调用图)。

3.2 三条分析路径与各自的失败边界

路径 A:组合式全量分析(插件声明了 analyzeFileFull 且文件需要调用图)。extract-structure-result.mjs#L153-L180。此路径下 analyzeFileFull 抛异常直接记为双 failed(因为能力已声明,异常就是真失败);而结构有效性与调用图有效性独立校验——full.structure 非法只会让 structureOutcomefailed,不影响 callGraphOutcome,反之亦然。这保证了一个坏字段不会污染另一个维度的统计。

路径 B:分离式结构分析。extract-structure-result.mjs#L182-L198

let callGraphOutcome = wantsCallGraph && supportsSeparateCallGraph
  ? 'failed'
  : 'skipped';

注意这个初始值的设计:只有当文件确实需要调用图(code/script 类目)插件声明了 extractCallGraph 时,初始预期才是 failed;否则直接置为 skipped。也就是说,“插件没有这个能力”永远不会变成失败——这正是计划中“缺失可选调用图能力必须跳过而非失败”的落地。

路径 C:文件不需要调用图(非 code/script 类目)。 此时 wantsCallGraph 为 false,callGraphOutcome 恒为 skipped,即使插件声明了 analyzeFileFull 也不会去调用它(测试用例 uses separate structure analysis for non-code files even when full analysis is advertised 显式验证了 fullCalls === 0)。

3.3 CLI 侧汇总:filesSkippedanalysisOutcomes

extract-structure.mjs 的批处理主循环把映射结果汇入输出 JSON,见 extract-structure.mjs#L110-L124

const { analysis, callGraph, structureOutcome, callGraphOutcome } =
  analyzeFileWithOutcomes(registry, file, content);

if (structureOutcome === 'skipped') {
  filesSkipped.push(file.path);
  continue;   // 不追加 result、不累加 outcome 计数
}

analysisOutcomes.structure[structureOutcome] += 1;
analysisOutcomes.callGraph[callGraphOutcome] += 1;

对应输出契约:

{
  scriptCompleted: true,
  filesAnalyzed: results.length,
  filesSkipped,                    // 含读取失败与“无插件支持”两类
  analysisOutcomes: {
    structure: { succeeded: 0, failed: 0 },
    callGraph:   { succeeded: 0, failed: 0, skipped: 0 },
  },
  results: [...],
}

关键点:structureOutcome === 'skipped' 的文件只进 filesSkipped,不进 results、也不参与 outcome 计数。这样基准侧的“账目平衡”校验(后文说明)依然成立,而 filesSkipped 正是 Schema 中 degraded 状态的合法触发条件。

3.4 测试用例印证语义边界

test_extract_structure_outcomes.test.mjs 覆盖了计划要求的全部边界:

测试场景 期望结果
注册表 getPluginForFile() 返回 null 结构、调用图均为 skippedL333-L352
选中的解析器只有 analyzeFile(无 analyzeFileFull 也无 extractCallGraph structureOutcome: 'succeeded'callGraphOutcome: 'skipped'L354-L382
已声明的 analyzeFileFull 抛异常/返回 null/undefined,且分离式调用成功 仍记 failed,不得被分离式成功“掩盖”(L428-L479
结构非法但调用图合法(及反向) 两个维度独立计分(L481-L542
37 种畸形结构条目 + 非法调用图条目 分别判 failed,且不串扰另一维度(L585-L678

四、基准测试侧:从批次汇总到报告状态

4.1 完整性校验:账目必须平衡

基准运行器 scripts/lib/large-repo-benchmark.mjs 中的 summarizeStructureOutput()L1257-L1358)对每批结构提取输出做严格核对。核心的平衡等式是:

structureSucceeded + structureFailed !== results.length ||
callGraphSucceeded + callGraphFailed + callGraphSkipped !== results.length
// → malformed = true

由于“skipped”的文件根本不进入 results(3.3 节),这两个等式恰好把 skipped 排除在“已分析”账目之外,而 skipped 计数通过 filesSkipped 单独入账。此外还核对:批次期望路径无缺失、无重复、无意外路径(missingStructurePaths / duplicateStructurePaths / unexpectedStructurePaths)。任何一条不成立都会把该批 complete 置为 false,最终升级为完整性失败——这满足“不削弱完整性检查”的约束。

4.2 状态判定:degradedfailed 与退出码

报告状态判定见 large-repo-benchmark.mjs#L1991-L2004

if (hasFailedIntegrity(report.integrity)) {
  report.status = 'failed';
  exitCode = 1;
} else if (
  report.warnings.length > 0 ||
  report.integrity.filesSkipped > 0
) {
  report.status = 'degraded';
  exitCode = 0;
} else {
  report.status = 'ok';
  exitCode = 0;
}

即:只要完整性检查通过,filesSkipped > 0(不支持文件被正确入账)只会得到 degraded + 退出码 0;只有完整性失败(含“已声明能力却失败”导致的 structureFailed/callGraphFailed 批次不完整)才落入 failed + 退出码 1。退出码语义在 docs/benchmarks/large-monorepo.md 的状态表中公开:

Exit code Meaning Report behavior
0 完成,状态 okdegraded JSON 与 Markdown 均写出
1 确定性阶段、完整性检查或临时产物清理失败 输出位置可写时写出部分报告
2 CLI 用法非法 不写报告

4.3 Schema 侧的配套约束

large-repo-report-1.0.0.schema.json 无需新增字段,仅靠既有分支即可表达新语义。其 degraded 分支(L144-L171)要求:报告必须至少有 1 条 warning, integrity.filesSkipped >= 1ok 分支则要求 warnings 为空且 filesSkipped 恒为 0(L119-L135)。这与 3.3/4.2 节的实现严格互锁:不支持文件被计为 skipped → filesSkipped > 0degraded 合法、ok 非法。测试 test_large_repo_report_schema.test.mjs 会对 ok / 空 / degraded / 部分 failed 四类报告逐一编译并校验该 Schema,而 test_large_repo_benchmark.test.mjs 覆盖运行器行为(注意:运行器本身不做运行时 Schema 校验,Schema 验证发生在基准测试中,这是 large-monorepo.md 明确说明的)。

4.4 文档语义对照

实施计划 Task 1 的 Step 5 要求在基准文档中写明新语义,对应 large-monorepo.md 中已有段落(“Structure diagnostics” 一节):

Structural coverage counts successful structure analyses; files without a registered structural parser are accounted in filesSkipped and produce a degraded report. Missing optional call-graph capability is also skipped. After a parser advertises a capability, an exception or invalid result remains an integrity failure instead of being reported as skipped work.

这句话与本节源码分析完全对应:无解析器 → filesSkipped + degraded;缺调用图能力 → skipped;声明后失败 → integrity failure。

五、计划文档中的实施步骤(可直接执行)

计划采用 TDD 流程(RED → GREEN),全部命令以 PowerShell 形式给出(计划面向 Windows 检出环境;其他平台等价替换为 npx vitest run ... 即可)。

Step 1:先加失败测试。tests/skill/understand/test_extract_structure_outcomes.test.mjs 中新增两条:一条让注册表 getPluginForFile() 返回 null,断言双 skipped;一条让选中的解析器支持结构但没有 analyzeFileFull/extractCallGraph,断言 succeeded + skipped

Step 2:验证 RED。 运行:

& .\node_modules\.bin\vitest.CMD run tests\skill\understand\test_extract_structure_outcomes.test.mjs

此时新测试应失败,因为旧版映射器会把无插件文件记为 failed

Step 3:最小实现。 按第 3.1/3.3 节所示修改 analyzeFileWithOutcomes()extract-structure.mjs 的汇总逻辑:已知不支持 → skipped 且计入 filesSkipped;需要调用图但插件未声明能力 → 调用图 skipped;保持既有计数使基准的形态校验(4.1 节平衡等式)继续成立。

Step 4:验证 GREEN。 同时跑三个测试文件:

& .\node_modules\.bin\vitest.CMD run tests\skill\understand\test_extract_structure_outcomes.test.mjs tests\benchmark\test_large_repo_report_schema.test.mjs tests\benchmark\test_large_repo_benchmark.test.mjs

期望:除已有文档化跳过外全部通过。

Step 5:文档化语义。 更新 docs/benchmarks/large-monorepo.md,写明“不支持的结构文件按 skipped 入账并产生 degraded;能力选中后的解析器失败仍是致命的”。

Step 6:全量验证。 串行跑根测试套件、构建 @understand-anything/core,然后对干净仓库运行基准(输出必须在被测工作树之外),用 Schema 1.0.0 校验 JSON,并确认 JSON/Markdown 成对报告的 pairId 一致。运行方式参考 large-monorepo.md 的官方命令:

corepack pnpm install --frozen-lockfile
corepack pnpm --filter @understand-anything/core build
corepack pnpm benchmark:large-repo /absolute/path/to/repository \
  --label public-repository-name \
  --output /absolute/path/to/benchmark-results/public-repository-name.json

Step 7:提交。 提交信息为:

fix(bench): distinguish unsupported analysis from failures

六、设计要点回顾

  1. 能力探测先行getPluginForFile() 返回 null 或插件未声明对应方法,是唯一的“跳过”入口;此后的一切异常都按失败处理,避免了“用跳过掩盖真实故障”的滑坡。
  2. 零 Schema 升级skipped 语义被拆到两个既有维度——结构侧进 filesSkipped,调用图侧进 callGraph.skipped 计数——既有 1.0.0 Schema 的 degraded 分支天然覆盖,JSON/Markdown 成对报告与 pairId 机制不受影响。
  3. 账目恒等式是安全网succeeded + failed === results.length(结构)、succeeded + failed + skipped === results.length(调用图)使“被悄悄丢掉的文件”在批次校验阶段即暴露。
  4. 降级但不静默degraded + 退出码 0 让 CI 可继续消费报告,同时 warningsfilesSkipped 字段保证读者能在比较性能前先检查 large-monorepo.md 所要求的 warningsintegrity 与 Markdown 摘要。

对维护者而言,这套语义的验证入口集中在三个测试文件:test_extract_structure_outcomes.test.mjs(映射层)、test_large_repo_report_schema.test.mjs(契约层)、test_large_repo_benchmark.test.mjs(运行层);如需深入解析器注册机制,可继续阅读 registry.tsplugins/discovery.ts

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341