Skip to content

feat(verification): recognize CTest and GoogleTest suites - #3

Open
RyanZhangK wants to merge 6 commits into
godv61:mainfrom
RyanZhangK:feat/verification-ctest-gtest
Open

RyanZhangK wants to merge 6 commits into
godv61:mainfrom
RyanZhangK:feat/verification-ctest-gtest

Conversation

@RyanZhangK

@RyanZhangK RyanZhangK commented Oct 25, 2026 •

Copy link
Copy Markdown

问题

testEvidence() 只识别 npm/pytest/go/cargo/maven 等运行器。C/C++ 项目(CMake 工程)的两种常见回执——ctest 摘要和 GoogleTest 二进制输出——都解析不出来,test_summary.count 恒为 null,dev_task verify 会以「没有可见的测试结果」拒绝,即使退出码为 0、全部用例通过。

真实复现(未打补丁的 0.34.0,CMake 工程 192 个用例):

$ ctest --test-dir build/platform
100% tests passed out of 192

$ build/platform/foray_platform_tests
[==========] 192 tests from 39 test suites ran. (301 ms total)
[  PASSED  ] 192 tests.

→ dev_task verify: ok=false, receipt.test_summary = { count: null }

调用方只能退回 verify: ["true"] 之类毫无意义的命令,测试门禁形同虚设。

改动(已合并 main,按 review 重做)

分支已 merge main(0.40.0),src/verification-tests.ts、.verification-test.mjs、README.md、docs/development.md 的冲突按 main 版本重写,再在 main 的代码结构上重新实现 CTest/GoogleTest,Go JSON、unittest、Gradle 等既有证据要求完整保留。

针对上轮 review 的五点:

  1. 同步 main、保留既有运行器要求:以 0.40.0 的 testEvidence() 为基底,新增分支插在 unittest 之后;Go -json、unittest 完整摘要、Gradle JUnit XML 的判定逻辑未改动。
  2. CTest 不再只取汇总总数:M tests failed 非零记 0;逐行 ***Skipped 的用例从总数中扣除(真实 ctest 在有跳过时仍打印 100% tests passed out of T);全部跳过、out of 0、No tests were found!!! 一律记 0。复核样本中的 count: 1 来自旧实现只取 T,现已修正为 0。
  3. GoogleTest 不再只认输出标记:要求命令必须真的运行被测程序——echo/printf/cat/sh -c/python -c 等打印器与已知其它运行器(npm、pytest、go、maven、cargo…)直接返回 null;输出侧要求完整汇总 [==========] N tests from M test suites ran.、[ PASSED ] N tests,出现 [ FAILED ] 或整个套件全部跳过记 0,空套件记 0。
  4. Windows 带引号路径:commandWords() + commandName() 已能把 "C:\Program Files\CMake\bin\ctest.exe" --test-dir build 解析为 ctest,并有用例覆盖。
  5. 真实运行冒烟 + 完整回归:新增两个真实运行测试(CMake/CTest 各一套 fixture、真实 GoogleTest 二进制),重建 hooks/commit-msg 并同步 .hook-consistency.mjs。

验证

  • npm run typecheck(server + client)通过;npm run build 通过。

  • npm test:426 通过、0 失败(含 .verification-test.mjs 12 个用例:23 条表驱动边界 + 真实 CTest/GoogleTest 冒烟)。

  • 真实输出冒烟(本机 CTest 3.x / GoogleTest 1.18):

    场景 真实输出 count 门禁
    CTest 1 通过 + 1 跳过 100% tests passed out of 2 + ***Skipped 1 通过
    CTest 全部跳过 100% tests passed out of 1 + ***Skipped 0 拒绝
    CTest 失败 0% tests passed, 1 tests failed out of 1 0 拒绝
    GoogleTest 3 测试、1 个 GTEST_SKIP 3 tests … ran. / [ PASSED ] 2 tests. 2 通过
    GoogleTest 全部跳过 [ PASSED ] 0 tests. + [ SKIPPED ] 0 拒绝

    实测确认 GoogleTest 的 [ PASSED ] 计数已排除 skipped 用例,因此不再二次扣减。

  • hooks/commit-msg 已按新源码重建,.hook-consistency.mjs 通过。

C/C++ projects gate on ctest, and each suite binary prints GoogleTest
summaries, but testEvidence() only knew npm/pytest/go/cargo/maven. A
green C++ receipt therefore carried test_summary.count = null and the
stage gate rejected it as "no visible test result".

Read both ctest summary forms -- "N% tests passed, M tests failed out
of T" and the collapsed clean-run form "100% tests passed out of T"
-- plus the GoogleTest "[==========] N tests from K test suites ran."
/ "[  PASSED  ] N tests" lines. Failed, skipped or empty runs still
count as zero so they cannot cross the gate.

Verified with the real receipts of a CMake project: 192/192 passed now
yields count 192 for both ctest and the gtest binary, while a failing
summary stays 0.

@godv61 godv61 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢贡献!CTest / GoogleTest 的结果识别对 C/C++ 项目很有价值,目前 0.40.0 仍有这个支持缺口。

近期 main 已有较多流程和验证逻辑调整,这个分支目前存在冲突。建议直接更新本 PR,无需为同一功能重新提 Issue。合并前请先处理以下事项:

  1. 同步最新 main 并解决冲突,保留当前 Go JSON、unittest 等运行器的证据要求。
  2. CTest 不能只取汇总中的总测试数。请补充全部跳过、部分跳过和空套件的用例,保证“全部跳过”不会成为非零通过证据。我用包含 Skipped 的解析样本复核时,当前实现仍返回了 count: 1。
  3. GoogleTest 目前仅凭输出标记识别,不限制实际命令。解析样本中,普通 echo 打印相同标记也会返回 count: 1。请约束非测试命令,并补充完整汇总、失败和跳过的边界测试。
  4. 请覆盖 Windows 带空格且带引号的路径,例如 "C:\Program Files\CMake\bin\ctest.exe" --test-dir build,当前匹配方式无法识别。
  5. 补充真实 CTest / GoogleTest 运行的冒烟验证,并运行当前完整回归及 Git hook 一致性检查;同时更新生成的 hook(如构建产生变化)。

以上结论来自补丁阅读和解析样本复核,尚未复跑描述中的完整 192 用例工程。修订并通过 CI 后再评估合并。本 PR 先保持开放,欢迎继续更新。

Resolve the conflicts in the verification tests, README and development
docs against 0.40.0, keeping the Go JSON, unittest and Gradle evidence
requirements from main, then rework the CTest/GoogleTest evidence as
requested in review:

- CTest counts only cases that actually passed. A failed run reports 0,
  skips are subtracted from the summary and an all-skipped or empty
  suite reports 0, so "N% tests passed" alone is never passing evidence.
- GoogleTest needs a real runner invocation and a complete summary.
  echo, cat, sh -c, python -c style printers and other known runners
  (npm, pytest, go, maven, ...) can no longer forge a passing count.
- Quoted Windows paths such as "C:\Program Files\CMake\bin\ctest.exe"
  are recognized as ctest.
- Add table-driven boundary cases, real CTest and GoogleTest smoke runs,
  and refresh the generated commit-msg hook.
@RyanZhangK

Copy link
Copy Markdown
Author

已按上轮五点意见重做,分支已 rebase 到最新 main 之上(merge commit),冲突文件全部按 main 版本重写后重新实现:

  1. 保留 Go JSON / unittest / Gradle 证据要求,新分支插在 unittest 之后。
  2. CTest:M tests failed 非零记 0,***Skipped 从总数扣除,全跳过/空套件记 0——复核样本的 count: 1 现为 0。
  3. GoogleTest:命令必须是真实被测程序,echo/cat/sh -c/python -c 及其它已知运行器返回 null;输出侧要求完整汇总,[ FAILED ] 或全跳过记 0。补了 23 条表驱动边界。
  4. "C:\Program Files\CMake\bin\ctest.exe" --test-dir build 已有用例覆盖。
  5. 新增真实 CTest 与真实 GoogleTest 冒烟测试(非 mock),npm test 426 通过 / 0 失败,hooks/commit-msg 已重建并通过 .hook-consistency.mjs。

细节与实测数据见 PR 描述。麻烦复审,谢谢!

@godv61 godv61 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢更新,这次已经复核到提交 160cba7d3e660cfd443fac3e3088c197b5b1f0e6。CTest / GoogleTest 支持的方向认可;同步 main、保留现有运行器判定和更新生成的 hook 都已核对,真实 CTest 的全部跳过、全部禁用、部分跳过计数也符合预期。

本次实际验证:

  • npm run typecheck、npm run build、hook 一致性检查通过,重建后的 hook 与提交内容一致。
  • 完整 npm test 为 426 通过、0 失败、9 跳过。跳过项包含新增 CTest / GoogleTest 真实冒烟,因此这个结果不能单独证明新增运行器已实机验证通过。
  • 明确提供本机已安装的 CMake/CTest 路径后,Windows 下的 CTest 冒烟失败;临时副本仅添加 -C Debug 后通过。
  • 实际运行 cmd /d /c type old-results.txt 和 PowerShell Get-Content,只读取旧 GoogleTest 日志、未执行测试,却都得到 count=5,且回执门禁接受;相同输入在 main 基线上为 count=null。

仍有两项需要修正,具体复现和位置见行内评论:

  1. P1:外壳读取旧日志仍被认作本次 GoogleTest 证据。 这会新增零测试误放行路径,需修复并补充回执门禁级回归。
  2. P2:CTest 冒烟未处理多配置生成器。 Windows 默认 Visual Studio 生成器下缺少 -C,导致测试全部 Not Run。

验证方面,还请至少在一个 CI job 中明确准备 CMake/GTest 并强制执行新增真实冒烟;GoogleTest 测试不要把所有 configure 失败都解释成缺少依赖而 skip,应区分依赖缺失和实际配置错误。本次未实际运行 GoogleTest C++ 二进制,不把解析样本通过写成实机通过。

本 PR 继续保持开放,当前先请求修改。请在修订提交上重新跑完整回归、Windows CTest 冒烟及 hook 一致性检查,提供实际执行、非 skip 的结果后再复审。如果暂时难以明确 GoogleTest 的命令边界,也可以先缩小为 CTest 支持,修复跨平台测试后单独评估。

Comment thread src/verification-tests.ts Outdated
const name = commandProgramName(command)
if (!name) return null
if (name === 'ctest') return ctestEvidence(output)
if (outputOnlyCommands.has(name) || runnerCommands.has(name) || skillReceiptProblem(command)) return null

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 对外壳内的实际命令执行相同的证据限制。

这里的两个集合只检查顶层程序名。cmd / powershell 不在集合中,skillReceiptProblem 也不拒绝外壳里的 type / Get-Content,所以普通读取旧日志的命令会进入 gtestEvidence。

Windows 上实际复现:创建 old-results.txt,内容如下,然后运行下面任意一条命令:

[==========] 5 tests from 2 test suites ran. (1 ms total)
[  PASSED  ] 5 tests.
cmd /d /c type old-results.txt
powershell -NoProfile -Command "Get-Content old-results.txt"

两条命令都只读取文件,退出码均为 0;当前 PR 的 testEvidence 为 {count: 5}、receiptHasRequiredTests 为 true、receiptProblems 为 [],verificationPlanProblem 也未拒绝。main 基线 0040b31 对相同命令及真实输出返回 {count: null}。复现范围是共享回执门禁函数,并未在业务任务中执行提交。

若支持外壳包装,应先识别实际执行程序并应用一致的限制;无法明确判断的包装不应获得通过证据。也可先限定为直接执行测试二进制并明确支持范围。请补充 cmd /c type、PowerShell Get-Content、sh -c 'cat ...' 等回归,断言回执门禁也拒绝。这里要求拒绝常见读日志命令,不是要求识别任意恶意二进制。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修。ctestOrGtestEvidence 不再取自顶层的第一个词:command-words.ts 的 invokedProgram 会跳过 NAME=value 和 env/nice/timeout 等透明前缀,递归拆开 cmd /c、sh -c、powershell -Command 并记下 shellDepth。GoogleTest 证据改为正向判定(深度 0 + 路径形态 + 不在读取类命令集合 + 不是运行器 + 不是脚本),所以 cmd /d /c type old-results.txt、powershell -NoProfile -Command "Get-Content old-results.txt"、sh -c "cat old-results.txt" 现在都是 null,回执门禁也拒收;外壳命令在 verificationPlanProblem 阶段就被拒绝。这四个形态和 cmd /c ctest …、sh -c "ctest …" 都进了 .verification-test.mjs 的负例表。

CI 双环境回执在下面的总评论里:run 42359142410,两个 cpp-evidence job 都 14 pass / 0 skipped。

Comment thread .verification-test.mjs Outdated
return dir
}
const run = dir => {
const result = spawnSync(ctest, ['--test-dir', dir, '--output-on-failure'], { encoding: 'utf8' })

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 为 CTest fixture 明确生成器或构建配置。

上面的 cmake -S ... -B ... 未固定生成器,Windows 默认使用 Visual Studio 时会生成多配置项目;此处未传 -C,测试不能运行。

用本机 Visual Studio Build Tools 随附的 CTest 3.31.6-msvc6,通过 CMAKE_BINARY / CTEST_BINARY 指向实际工具后运行本测试,得到:

***Not Run
Test not available without configuration.  (Missing "-C <config>"?)
0% tests passed, 2 tests failed out of 2

CTest 退出码为 8,随后第 164 行失败。在临时副本中仅把参数改为 ['--test-dir', dir, '-C', 'Debug', '--output-on-failure'],整个真实 CTest 冒烟即通过,覆盖混合、全跳过和失败场景。

请显式选择可用的单配置生成器,或指定构建配置,并在 Windows CI 上实际执行这条冒烟。默认 PATH 找不到 CMake 而 skip 的绿色结果无法覆盖该问题。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修。fixture 现在从 CMakeCache.txt 里的 CMAKE_CONFIGURATION_TYPES 判断多配置:多配置时 ctest -C Debug、cmake --build --config Debug,二进制按 build/Debug/*.exe 定位;生成器/工具链可用 CMAKE_GENERATOR、CMAKE_TOOLCHAIN_FILE 注入,不固定生成器。同时 REQUIRE_CPP_EVIDENCE=1 时工具缺失直接失败,configure 失败只有确实缺 GTest/编译器才 skip,Windows 的无条件 skip 已删。

CI 新增 cpp-evidence 矩阵:ubuntu-latest 用 Ninja(单配置),windows-latest 故意走默认 Visual Studio 生成器(多配置),两边都带 REQUIRE_CPP_EVIDENCE=1 真跑,run 42359142410 全绿。

RyanZhangK and others added 4 commits November 7, 2026 16:46
CTest and GoogleTest evidence was attached to any command whose text merely
contained the markers, and to shell wrappers that only read an old log:

  cmd /d /c type old-results.txt            -> count 5
  powershell -Command "Get-Content old"     -> count 5
  sh -c "cat old-results.txt"               -> count 5
  node fake.mjs npm test                    -> count 5

main returned null for all four, so the branch accepted receipts the base
branch rejected.

- command-words.ts: add `invokedProgram`, which resolves the program that
  actually runs, skipping NAME=value assignments and the transparent prefixes
  env/nice/timeout/nohup/command/exec/stdbuf, and recursing into cmd /c,
  sh -c and powershell -Command bodies with a shell depth.
- verification-tests.ts: GoogleTest evidence now needs a positive signal --
  depth 0, a path-like program, not a reader (type, cat, Get-Content, ...),
  not a runner, and not a script file. Maven, Gradle, npm/yarn/pnpm, pytest
  and cargo evidence is anchored the same way instead of substring-matching
  the whole command line. Shell wrappers are rejected while the plan is
  checked, so the failure surfaces before the command runs.
- .verification-test.mjs: cover the four wrappers, the reader commands,
  wrapper-wrapped ctest and the forged summaries from a real execution.
The previous smoke test skipped whenever a tool was missing and pinned no
generator, so it proved nothing and failed on a multi-config default:

- REQUIRE_CPP_EVIDENCE=1 turns a missing toolchain into a failure.
- The generator and toolchain file come from CMAKE_GENERATOR and
  CMAKE_TOOLCHAIN_FILE, and the smoke test asks for `-C Debug` / `--config
  Debug` only when the cache lists CMAKE_CONFIGURATION_TYPES.
- A configure failure is only skipped when GTest or a C++ compiler is really
  missing; anything else fails. The unconditional Windows skip is gone.

New cpp-evidence job: ubuntu-latest with Ninja and libgtest-dev, and
windows-latest with the default Visual Studio generator plus vcpkg GTest,
which is exactly the multi-config case that used to report every test as
"***Not Run".
The GitHub runners ship CMake without libgtest-dev, so configure stops in
CMake's FindGTest module with "Could NOT find GTest (missing: ...)" instead of
the package-configuration message. Only that wording skipped, so the whole
verify job failed on an environment that legitimately lacks the package.
@RyanZhangK

Copy link
Copy Markdown
Author

两条行内问题都已在 1e9504b 上修好,CI 现在在真实 Windows 和 Linux 上跑通了 CTest/GoogleTest 冒烟。

P1 外壳里的命令绕过了限制

根因是 ctestOrGtestEvidence 用 commandProgramName 取整串命令的第一个词,然后靠负向黑名单兜底:cat 在名单里,type、Get-Content 不在,cmd /c、sh -c、powershell -Command 更是完全没被拆开——外壳本身不是 cat,于是继续走 gtestEvidence,只读旧日志就能拿到 count: 5,回执门禁也照收。

改法是换成正向识别:src/command-words.ts 新增 invokedProgram,解析出真正被调用的程序(跳过 NAME=value、env/nice/timeout/nohup/command/exec/stdbuf 等透明前缀,递归拆外壳并记录 shellDepth)。GoogleTest 证据现在必须同时满足:深度 0、程序是路径形态、不在读取类命令集合(type/cat/Get-Content/findstr/jq…)、不是其它运行器、不是脚本文件。外壳内的命令在验证计划阶段就被 verificationPlanProblem 拒绝,不会等到执行完才发现。同一处兜底也收紧了 Maven/Gradle/npm/pytest/Cargo:不再对整行做子串匹配,而是锚定被调用程序与测试目标,所以 node fake.mjs npm test 打印一份 TAP 摘要不再算数。

P2 CTest fixture 在 Windows 多配置下必挂

根因是 fixture 既不固定生成器也不用 -C:Windows 默认的 Visual Studio 生成器是多配置,ctest 拿不到配置就把每个用例报成 ***Not Run,0% tests passed, 2 tests failed out of 2,退出码 8。现在 fixture 从 CMakeCache.txt 的 CMAKE_CONFIGURATION_TYPES 判断是否多配置,多配置时 ctest -C Debug、cmake --build --config Debug,二进制路径也按 build/Debug/*.exe 找;生成器和工具链文件可由 CMAKE_GENERATOR、CMAKE_TOOLCHAIN_FILE 注入。

另外按你说的把 skip 收紧:REQUIRE_CPP_EVIDENCE=1 时工具缺失直接 assert.fail,configure 失败只有在确实缺 GTest/编译器时才 skip,Windows 的无条件 skip 已删除,GTest 的"configure 失败一律 skip"也不再有。

CI 回执(真实 Windows + Linux)

新增 job cpp-evidence,矩阵 ubuntu-latest + windows-latest,都带 REQUIRE_CPP_EVIDENCE=1:

  • Linux:装 cmake ninja-build libgtest-dev,CMAKE_GENERATOR=Ninja(单配置)
  • Windows:vcpkg install gtest + vcpkg 工具链,故意不指定生成器,走默认 Visual Studio 多配置——正是上面 P2 那个会挂的场景

Run:https://github.com/RyanZhangK/dsh-task-engine/actions/runs/42359142410(head 1e9504b,结论 success)

job 结果
cpp-evidence (ubuntu-latest) ✓ 22s
cpp-evidence (windows-latest) ✓ 2m11s
verify (ubuntu/windows × node 22/24) ✓ 全绿
preset-compat / dsh-contract 固定版本 ✓

两个 cpp-evidence job 都输出 tests 14 / pass 14 / fail 0 / skipped 0;因为 REQUIRE_CPP_EVIDENCE=1,出现任何 skip 都会变成失败,所以这 14 条是真跑了 CTest 和 GoogleTest。

中间还有一次失败值得记一笔:42358828876 里 verify 挂掉,因为 runner 上 CMake 找不到 GTest 时报的是 Could NOT find GTest (missing: ...)(FindGTest 模块路径),而我只匹配了 package-config 那句,于是把"缺依赖"当成了真实失败。1e9504b 补上了这条文案。

本地回执

  • npm run typecheck、npm run build 通过(hooks/commit-msg 已重建)
  • REQUIRE_CPP_EVIDENCE=1 node --test .verification-test.mjs → 14 pass / 0 skip
  • npm test → 451 tests / 442 pass / 0 fail / 9 skipped(9 条为既有环境型 skip),hook-consistency 通过
  • 新增负例:cmd /d /c type …、cmd /c type …、powershell -Command "Get-Content …"、sh -c "cat …"、bash -c "type …"、裸 type/more/findstr/Get-Content、cmd /c ctest …、sh -c "ctest …"、suite_tests、./run-tests.sh、node ./build/suite_tests 全部为 null;外加一条真跑脚本的伪造回执测试和一条计划期外壳拒绝测试。

文档同步在 README.md、docs/development.md:外壳包装、读取旧日志、其它运行器打印同样文字都不算证据;多配置下 CTest 需 -C。

如果还想把范围先缩到只支持 CTest,我可以把 GoogleTest 那条正向识别拆成独立 commit 再删掉——现在两条都在同一支上,分开也好回退。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants