日志里的 success 不能替代验收:分别记录退出码、stdout 与 stderr
摘要:自动化调用命令时,搜索成功文字、只看退出码或只查错误输出,都可能误判。本文用四个真实运行的合成子进程,说明如何按工具契约保存原始证据,并区分业务成功与预期失败用例通过。
一个脚本先打印“success”,随后检查失败并退出。如果调用方只搜索日志里的成功词,就会把失败任务继续交给下一步。构建、导出、数据核对等流程,都需要回答同一个问题:凭什么认定这次调用达到了目标?
三个信号,分别保存
退出码描述进程按约定如何结束,0通常表示正常结束,其他数值的具体含义由工具定义。stdout常用于结果,stderr常用于诊断,但这只是常见安排。错误可以用JSON写进stdout,提示也可以写进stderr,流的名字不能替代接口契约。
例如,本地保留的evidence-ledger版本约定:0表示账本一致,1表示不一致,2表示参数或输入等错误,3表示报告写入等失败;除帮助模式外,stdout输出一个JSON对象。只判断stderr是否为空,会漏掉其中正常返回的结构化错误。这里引用的是保留版本的说明与源码,不能推广为所有CLI的规则。
四种组合,先说清预期
我用Python标准库启动四个独立合成子进程,分别保存退出码和两条原始字节流。教学验收契约设为“退出码为0,stdout是JSON对象,且ok严格为布尔值true”,允许stderr包含提示。
| 场景 | 退出码 | stdout内容 | stderr内容 | 按上述契约 |
|---|---|---|---|---|
| 先报成功再失败 | 7 | success: preparation complete | validation rejected | 拒绝 |
| 结构化拒绝 | 2 | ok=false及输入错误 | 空 | 拒绝 |
| 成功但附提示 | 0 | ok=true、items=2 | notice: using default | 接受 |
| 正常退出但业务未完成 | 0 | ok=false及未完成原因 | 空 | 拒绝 |
表中JSON内容为字段摘要,原始输出另行保留。第一行证明成功文字可能只描述准备阶段;第二行说明没有stderr也能失败;第三行说明有stderr不必然失败;第四行说明单看0仍不够。这些是主动构造的反例,没有发现或宣称某个真实项目存在同样缺陷。
先落证据,再做解释
对于输出很小、执行时间有限的命令,可从下面的采集方式开始。cmd是参数数组,包含程序路径和各个参数,不能把凭据写入公开记录。
import subprocess
from pathlib import Path
p = subprocess.run(
cmd, capture_output=True, check=False, timeout=5
)
Path("stdout.bin").write_bytes(p.stdout)
Path("stderr.bin").write_bytes(p.stderr)
exit_code = p.returncode
每次调用应使用自己的记录目录。check=False让非零退出码留给调用方按预期判断;超时和启动失败仍会抛出异常,必须单独记录,不能伪造一个正常退出码。
随后按约定编码解码stdout,解析JSON,并检查对象结构、必需字段和业务断言。ok要严格检查布尔值,不能让字符串"false"因非空而被当作真。解析失败应记为协议失败,同时保留原始输出,方便区分额外日志、编码问题和截断结果。
示例使用的“允许提示”是自定规则。若工具明确要求stderr为空,就把它加入断言;若允许诊断消息,仍须保存。将stderr合并进stdout会丢失来源,还可能污染JSON。分开捕获也不能还原两条流之间的精确先后顺序。
验收通过,要说明通过了什么
负向用例有不同预期。故意输入坏数据时,预期退出码2且返回指定错误,可以算“拒绝行为验证通过”;它不表示坏数据已处理成功。记录中应并列写出预期、实际、业务状态和断言结果,而不是只留一个绿色PASS。
证据还应关联程序版本、参数、工作目录及输入指纹,避免拿另一轮输出解释本轮状态。大输出或长时间运行的任务需要文件重定向、容量限制与超时处理;capture_output会把输出放入内存,并非通用的资源上限方案。
本次于2026年10月31日在Windows、Python 3.12.14下运行上述四个合成进程,四项观察均符合预期,其中只有一项满足教学成功契约。未重跑evidence-ledger全套测试,未执行远程命令,也未验证真实部署。原始流与哈希便于复查本次材料,不能单独证明业务事实。
- 点赞
- 收藏
- 关注作者
评论(0)