Codex 验收前的一步:工作区测试通过,暂存区版本也通过了吗?
给绿色测试加上版本标签,再判断它能否证明下一次提交。
- 把测试结果绑定到实际源码版本
- 识别 HEAD 与工作区相同但索引不同的抵消案例
- 保留两层 patch、NUL 路径与获准内容
- 区别单文件版本探针和完整索引测试
1. 测试绿了,准备提交的那份代码却可能没变
让 Codex 修复一个断言,终端显示通过,编辑器里也只剩正确的值。准备提交时,最容易省掉的一步,是确认刚才运行的文件与暂存区里的文件是不是同一份。测试通常从工作目录读取代码;已经暂存的内容不会随着后续编辑自动更新。
本文把这个差别缩成一个可以完整复跑的案例:cancel.py 在 HEAD 中是 result = 1,暂存区中是 result = 999,工作区又被改回 result = 1。对这个路径执行 git diff HEAD,输出为空;从工作区取出的源码通过断言,从索引取出的源码失败。只要普通提交仍使用当前索引,这个错误版本就仍是候选提交内容。
以下实验于 2026 年 10 月 7 日在 Git 2.52.0、Python 3.12.14、Linux 上实际运行。它只创建并删除自己的临时合成仓库,不接受已有仓库路径,不安装依赖,不启动 Codex 或其他模型。文中的测试结果来自 Git 与 Python 子进程,不能解读为对模型审阅能力的测评。
2. 先把验收对象说清楚
HEAD、索引、工作区可以分别理解为最近提交、下一次普通提交的候选内容、当前磁盘上的内容。这里的“普通提交”指没有 -a、没有另行指定路径、没有其他工具或钩子改写索引的情形。commit -a、路径提交、提交钩子以及提交前继续编辑,都可能改变最终对象;不能把本例的结论机械套给所有提交方式。
两层 patch 把过程留下来了:cached 比较 HEAD 到索引,显示 1 被改成 999;默认 diff 比较索引到工作区,显示 999 又被改成 1。HEAD 到工作区的端点比较会抵消这两步。本文另外放了几个辅助文件,因此“空输出”只针对 cancel.py,不是声称整个合成仓库的 git diff HEAD 都为空。
git diff --cached --no-ext-diff --no-textconv -- cancel.py
git diff --no-ext-diff --no-textconv -- cancel.py
git diff HEAD --no-ext-diff --no-textconv -- cancel.py
在真实协作中,先问“要证明当前工作区能运行,还是要证明准备提交的版本能运行”。问题选定后,再给测试记录写上对应版本。一个没有版本标签的绿色结果,只能证明某次运行结束时退出码为零。
3. 在空目录复跑,不接触现有项目
准备一个空目录,把后文四份文件按标题保存到同一目录:lab.py、verify.py、expected.json、run_matrix.py。保留 UTF-8 编码,在该目录用下面的命令启动。完整源码、夹具内容和独立预期都在本文。
python run_matrix.py
运行器会新建 clean 与 poisoned 两个目录,每轮在系统 /tmp 下创建独立仓库,退出时清理临时仓库,只保留 evidence 观察记录。目录已存在就报错,避免覆盖上一次结果;要复跑,请换一个新空目录并使用这四份文件。环境要求是可信 PATH 中的 Git 和 Python 3,不支持把未知 Git 二进制当作安全输入。
预期的关键输出如下。索引探针退出码 1 是我们特意构造的失败;整个实验和验证器仍应成功退出。完整 stderr 保存在 report.json。
HEAD: result = 1
INDEX: result = 999
WORKTREE: result = 1
工作区探针 rc = 0;索引探针 rc = 1
未跟踪路径数 = 5
PASS: independent values, two patches, NUL paths, test binding and unchanged observation
PASS: clean / poisoned semantic reports match; per-run index bytes and mtimes preserved
实验不是把已有仓库拷贝出去再跑测试。它只取 cancel.py 的两份精确源文本,各自在新 Python 进程中接上同一句 assert result == 1。这样足够证明版本错位,却不能证明整个暂存区的依赖、导入关系、构建产物或项目测试套件通过。我们刻意不把单文件检查命名为“索引全量测试”。
4. 让结果回到它实际运行的内容
report.json 为每次探针记录来源、路径、源码原文、源码 SHA256、断言、实际执行代码的 SHA256、Python 参数、运行目录标识、stdout、stderr 与退出码。记录还绑定同一次观察的 HEAD、status.z 和两层 patch 哈希。这里使用 python -I -B -c,在临时根目录运行已知的合成文本;它不从用户项目导入模块。
证据文件中的 <synthetic-root> 是临时路径的规范化标识,不是读者应输入的路径。真实工程的测试记录应保存实际运行目录、可复现命令、代码版本和依赖版本;仅写“pytest 通过”不足以判断究竟验证了哪份内容。
verify.py 从独立手写的 expected.json 读预期。它检查三份源码、两层 patch 中的确切增删行、两个探针退出码、五个未跟踪路径及内容摘要,还复算测试与观察记录之间的哈希。该验证器用于发现复跑偏差或文件损坏,不是防伪签名;能同时改写结果与校验器的人仍能伪造一份自洽报告。
5. 收集时要保留内容,不能只留状态字母
snapshot 目录里保存 status.z、staged.patch、working.patch、两份 name-status NUL 流、untracked.z、未跟踪内容 JSON 与文件哈希清单。两份 patch 使用 --binary 和 --full-index 保存 Git 输出;这不表示本例验证了所有二进制格式。未跟踪清单限定为未被忽略的文件,ignored/cache.txt 是明确排除的哨兵。
辅助夹具包含一个同时拥有两层改动的 both.txt、一个只暂存的文件、两种删除和一次暂存改名后继续编辑。它们用于确认输出分层,本文的验收问题仍集中在 cancel.py。改名是相似度检测的结果,实验固定 --find-renames=50%,不会把一次 R100 判定当作永久文件身份。
五个未跟踪文件包含空格、制表符、换行和前导短横线。路径流按 NUL 分隔,不能按换行拆分,也不能塞进 shell 变量后假设字节不变。porcelain v1 的改名 NUL 记录还有第二个路径,且新路径在前;name-status 的改名记录也占多个字段。实验保存这两种原始输出,避免发明一个只会处理普通文件名的通用解析器。
paths = raw_untracked.split(b'\0')[:-1]
# 此处只用于 ls-files --others -z 的单路径记录
# 不要把这行原样拿去解析含 rename 的 status/name-status
文件名清单无法代替内容审阅,哈希也无法还原源文件。此处所有未跟踪内容均为已知合成文本,所以逐份保留。真实项目中的秘密、大文件或未获准目录,应先明确范围,不能为收集证据顺手读取或上传。
观察开始前与探针结束后,脚本比较 index 文件的字节哈希和 mtime,并比较工作区所有合成普通文件的内容哈希与 mtime。随后另做一个破坏性负例:继续改写已有未暂存修改的 both.txt,status 完全相同,patch 却改变了。这个负例发生在观察窗口之后,不混入前面的测试证据。
这是一份安静环境中的观察记录,不是文件系统原子快照。若编辑器、Codex 或其他进程同时写入,前后检查也不能证明中间没有变化;应暂停写入后重采,并在提交前再次核对候选内容。
6. 环境隔离和退出码的两个细节
lab.py 为 Git 子进程构造环境白名单,不继承 GIT_DIR、GIT_WORK_TREE、GIT_INDEX_FILE 或 GIT_CONFIG_* 等重定向。它使用独立 HOME/XDG、禁用全局与系统 Git 配置、空模板与空 hooksPath,并设置 --no-pager、--no-optional-locks、禁用 fsmonitor、--no-ext-diff 和 --no-textconv。实验还把外部 diff、textconv 和 fsmonitor 配置指向合成哨兵,确认该哨兵未执行。
run_matrix.py 对正常与受污染的父环境分别运行。两轮的内容、patch、测试结果和规范化命令记录一致。两个临时仓库有不同的文件时间,index 中还含文件系统 stat 缓存,因此不要求两轮 index 哈希相同;要求的是每轮采集前后 index 字节与 mtime 均不变。正文提供的比较器明确排除跨仓库的这些差异,而每轮验证器仍保留原值逐项检查。
这些控制只为合成实验可复现。已有仓库的忽略规则、属性和过滤器可能依赖其配置,清空它们会改变语义;禁用 external diff 与 textconv 也不是运行恶意仓库的沙箱。真实仓库不能仅凭这份脚本就获得“安全全覆盖”的结论。
退出码也需要结合命令解释:普通 git diff 在有差异时仍可返回 0;--exit-code 和 --no-index 用 1 表示差异。但本机 Git 2.52.0 对 --no-index 的缺失输入同样返回 1,stderr 为 Could not access。脚本故意保存这一负例。不要用“1 就是成功发现差异”或 || true 吞掉诊断。
git diff --no-index --no-ext-diff --no-textconv -- /dev/null approved-file
# 同时记录 stdout、stderr 和退出码;输入缺失不能算正常差异
--no-index 直接比较文件系统路径,不提供仓库根目录保护。实验比较仓库外文件时,两端仍在同一个获准的临时合成根目录内。路径是否获准来自任务授权,不来自 no-index;显式使用仓库外路径本身也不等于越权。不要把例子改成对任意真实目录递归读取。
7. 给 Codex 的交接语句要包含版本目标
把这个练习迁回日常项目时,先给出仓库与允许路径,并确认目前验收的是哪一层。下面是一段可按实际路径填写的请求;执行项目测试会运行项目代码,仍需放在已经批准的环境和测试范围内。
请检查已批准仓库中的指定路径,目标是确认下一次普通提交的候选内容。
先只读保存 HEAD、暂存区 patch、工作区 patch 和非忽略未跟踪清单。
不要暂存、stash、清理、提交或改写我现有的修改。
把已经运行的测试标明为工作区测试,并记录命令、目录、源码版本和退出码。
若索引与工作区不同,逐项说明哪些测试证据不能用于索引版本。
需要执行额外代码或导出完整索引树时,先说明方案和未覆盖项。
发现索引仍为 999 后,修复取决于提交意图。如果 1 才是要提交的值,可以在确认该路径的全部差异后重新暂存获准内容,再验证更新后的候选版本。如果某些改动刻意没有暂存,就不该用一次宽泛的 git add 把它们一起带入提交。本文不自动替读者做这个决定。
需要真正验证完整索引时,测试代码、被测模块、依赖配置以及构建输入都必须来自定义清楚的同一个版本。导出目录应独立于原工作区,不能借测试改变原 index 或覆盖用户文件。符号链接可能指向目录外,子模块条目只有提交引用,并不等于已获取其中代码;未合并索引、稀疏检出、skip-worktree、assume-unchanged、自定义过滤器和并发修改都需要另行处理。本文没有实现这种完整导出器,也没有验证这些场景。
最后保留一句能复查的话:“这条命令,在这个版本上,以这个退出码结束。”工作区的绿色结果仍然有价值;只有把它和要提交的版本对应起来,才知道它能回答哪个验收问题。
8. 完整源码一:合成夹具与观察记录 lab.py
此文件包含全部初始内容和变更步骤。它只创建自己的临时仓库;不提供现有仓库入口。
#!/usr/bin/env python3
"""Synthetic-only experiment: never accepts an existing repository path."""
import hashlib, json, os, pathlib, platform, subprocess, tempfile, sys
OUT = pathlib.Path.cwd() / 'evidence'
OUT.mkdir(exist_ok=False) # Never overwrite an earlier observation.
records=[]
def sha(b): return hashlib.sha256(b).hexdigest()
with tempfile.TemporaryDirectory(prefix='index-test-lab-',dir='/tmp') as tmp:
root=pathlib.Path(tmp); repo=root/'repo'; repo.mkdir(); home=root/'home';home.mkdir();hooks=root/'hooks';hooks.mkdir()
# Allowlist, rather than merely clearing three Git redirect variables.
env={'PATH':os.environ['PATH'],'HOME':str(home),'XDG_CONFIG_HOME':str(home),
'LC_ALL':'C','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':os.devnull,
'GIT_TERMINAL_PROMPT':'0','GIT_OPTIONAL_LOCKS':'0','GIT_PAGER':'cat',
'GIT_AUTHOR_DATE':'2026-01-01T00:00:00Z','GIT_COMMITTER_DATE':'2026-01-01T00:00:00Z'}
prefix=['git','--no-pager','--no-optional-locks','-c',f'core.hooksPath={hooks}',
'-c','core.fsmonitor=false','-c','color.ui=false','-C',str(repo)]
def git(*args,ok=(0,)):
r=subprocess.run(prefix+list(args),env=env,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
records.append({'argv':['git',*args],'rc':r.returncode,'stdout':r.stdout.decode('utf-8','backslashreplace'),'stderr':r.stderr.decode('utf-8','backslashreplace')})
if r.returncode not in ok: raise RuntimeError(records[-1])
return r.stdout
def put(p,s): (repo/p).write_text(s)
git('init','-b','main','--template=');git('config','user.name','Synthetic Lab');git('config','user.email','lab@example.invalid');git('config','commit.gpgsign','false')
bases={'.gitattributes':'*.txt diff=blocked\n','only-staged.txt':'base\n','both.txt':'HEAD\n','cancel.py':'result = 1\n','staged-delete.txt':'remove staged\n','working-delete.txt':'remove working\n','old-name.txt':'rename payload\n','.gitignore':'ignored/\n'}
for p,s in bases.items():put(p,s)
git('add','--','.');git('commit','-m','synthetic baseline')
baseline=git('rev-parse','HEAD').decode().strip()
helper=root/'external-helper.sh';marker=root/'external-helper-ran'
helper.write_text('#!/bin/sh\nprintf invoked > '+str(marker)+'\nexit 7\n');helper.chmod(0o700)
git('config','diff.external',str(helper));git('config','diff.blocked.textconv',str(helper))
git('config','core.fsmonitor',str(helper))
# Default diff can be empty although staged and untracked changes exist.
put('only-staged.txt','staged\n');git('add','--','only-staged.txt');put('new file.txt','untracked evidence\n')
empty_default=git('diff','--no-ext-diff','--no-textconv')
assert empty_default==b''
phase1={'default_diff_empty':True,'cached_nonempty':bool(git('diff','--cached','--no-ext-diff','--no-textconv')),'status':git('status','--porcelain=v1','-z','--untracked-files=all').decode()}
put('both.txt','INDEX\n');git('add','--','both.txt');put('both.txt','WORKTREE\n')
put('cancel.py','result = 999\n');git('add','--','cancel.py');put('cancel.py',bases['cancel.py'])
git('rm','--','staged-delete.txt');(repo/'working-delete.txt').unlink()
git('mv','--','old-name.txt','new-name.txt');put('new-name.txt','rename payload\nworking extra\n')
weird=['space name.txt','tab\tname.txt','line\nname.txt','-leading.txt']
for p in weird:put(p,'synthetic untracked\n')
(repo/'ignored').mkdir();put('ignored/cache.txt','intentionally excluded\n')
diff=['diff','--no-ext-diff','--no-textconv','--no-color','--find-renames=50%']
def names(*a):return git(*diff,*a,'--name-only','-z').split(b'\0')[:-1]
working=names();staged=names('--cached');net=names('HEAD')
untracked=git('ls-files','--others','--exclude-standard','-z').split(b'\0')[:-1]
assert b'cancel.py' in working and b'cancel.py' in staged and b'cancel.py' not in net
assert b'only-staged.txt' not in working and b'only-staged.txt' in staged
assert b'both.txt' in working and b'both.txt' in staged
assert b'new file.txt' not in working+staged+net and b'new file.txt' in untracked
assert set(untracked)=={p.encode() for p in weird+['new file.txt']}
assert b'staged-delete.txt' in staged and b'working-delete.txt' in working
ns=git(*diff,'--cached','--name-status','-z');assert b'R100\0old-name.txt\0new-name.txt\0' in ns
index_bytes=(repo/'.git/index').read_bytes();index_stat=(repo/'.git/index').stat()
wt_before={str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}
snap=OUT/'snapshot';snap.mkdir(exist_ok=True)
for name,args in [('status.z',['status','--porcelain=v1','-z','--untracked-files=all']),('staged.patch',diff+['--cached','--binary','--full-index']),('working.patch',diff+['--binary','--full-index']),('untracked.z',['ls-files','--others','--exclude-standard','-z']),('staged-names.z',diff+['--cached','--name-status','-z']),('working-names.z',diff+['--name-status','-z'])]:
(snap/name).write_bytes(git(*args))
untracked_manifest=[{'path':os.fsdecode(p),'bytes':len((repo/os.fsdecode(p)).read_bytes()),'sha256':sha((repo/os.fsdecode(p)).read_bytes()),'content_utf8':(repo/os.fsdecode(p)).read_text()} for p in untracked]
(snap/'untracked-content.json').write_text(json.dumps(untracked_manifest,ensure_ascii=True,indent=2)+'\n')
assert (repo/'.git/index').read_bytes()==index_bytes and (repo/'.git/index').stat().st_mtime_ns==index_stat.st_mtime_ns
assert wt_before=={str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}
# Worktree source passes its assertion, staged source demonstrably does not.
working_source=(repo/'cancel.py').read_text();index_source=git('show',':cancel.py').decode()
assert working_source=='result = 1\n' and index_source=='result = 999\n'
probes=[]
for label,source in [('working',working_source),('index',index_source)]:
test_code=source+'assert result == 1\n'
r=subprocess.run([sys.executable,'-I','-B','-c',test_code],cwd=root,env=env,capture_output=True)
probes.append({'tree':label,'path':'cancel.py','source':source,'source_sha256':sha(source.encode()),'assertion':'assert result == 1\n','executed_code_sha256':sha(test_code.encode()),'command':['python','-I','-B','-c','<source + assertion>'],'cwd':'<synthetic-root>','rc':r.returncode,'stdout':r.stdout.decode(),'stderr':r.stderr.decode(),'head':baseline,'status_sha256':sha((snap/'status.z').read_bytes()),'staged_patch_sha256':sha((snap/'staged.patch').read_bytes()),'working_patch_sha256':sha((snap/'working.patch').read_bytes())})
assert [p['rc'] for p in probes]==[0,1]
observation={'index_before_sha256':sha(index_bytes),'index_after_sha256':sha((repo/'.git/index').read_bytes()),'index_before_mtime_ns':index_stat.st_mtime_ns,'index_after_mtime_ns':(repo/'.git/index').stat().st_mtime_ns,'worktree_before':wt_before,'worktree_after':{str(p.relative_to(repo)):{'sha256':sha(p.read_bytes()),'mtime_ns':p.stat().st_mtime_ns} for p in repo.rglob('*') if p.is_file() and '.git' not in p.relative_to(repo).parts}}
assert observation['worktree_before']==observation['worktree_after']
assert observation['index_before_sha256']==observation['index_after_sha256']
assert observation['index_before_mtime_ns']==observation['index_after_mtime_ns']
# Status alone does not bind content: change a preexisting unstaged edit.
status_before=git('status','--porcelain=v1','-z','--untracked-files=all');patch_before=git(*diff)
put('both.txt','WORKTREE changed again\n')
assert git('status','--porcelain=v1','-z','--untracked-files=all')==status_before
assert git(*diff)!=patch_before
# Exit codes: plain diff returns zero with changes; exit-code/no-index use 1.
git(*diff,'--','cancel.py');plain_rc=records[-1]['rc']
git(*diff,'--exit-code','--','cancel.py',ok=(1,));difference_rc=records[-1]['rc']
git(*diff,'--exit-code','HEAD','--','cancel.py');net_rc=records[-1]['rc']
outside=root/'outside.txt';outside.write_text('outside repo but synthetic and authorized\n')
git('diff','--no-index','--no-ext-diff','--no-textconv','--',os.devnull,str(repo/'new file.txt'),ok=(1,));noindex_rc=records[-1]['rc']
git('diff','--no-index','--no-ext-diff','--no-textconv','--',str(repo/'new file.txt'),str(outside),ok=(1,));outside_rc=records[-1]['rc']
git('diff','--no-index','--no-ext-diff','--no-textconv','--',str(outside),str(root/'missing'),ok=(1,));error_rc=records[-1]['rc'];error_stderr=records[-1]['stderr']
# Missing no-index input can also return 1 on this Git: retain stderr, don't label all 1 as success.
assert not marker.exists(), 'External helper unexpectedly ran'
report={'git_version':git('--version').decode().strip(),'python':platform.python_version(),'platform':platform.platform(),'head':baseline,'three_versions':{'HEAD':git('show','HEAD:cancel.py').decode(),'INDEX':index_source,'WORKTREE':working_source},'observation':observation,'phase1':phase1,'paths':{'working':[os.fsdecode(p) for p in working],'staged':[os.fsdecode(p) for p in staged],'net_HEAD':[os.fsdecode(p) for p in net],'untracked':[os.fsdecode(p) for p in untracked]},'assertions':{'staged_cancel_invisible_in_HEAD_diff':True,'same_path_three_versions':True,'five_untracked_files_NUL_roundtrip':True,'staged_R100_rename_and_unstaged_edit':True,'snapshot_does_not_change_index_bytes_or_mtime':True,'snapshot_does_not_change_worktree':True,'external_diff_textconv_fsmonitor_helpers_not_run':True,'same_status_different_patch':True,'working_result':1,'index_result':999},'exit_codes':{'ordinary_diff_changed':plain_rc,'diff_exit_code_changed':difference_rc,'diff_HEAD_cancelled':net_rc,'no_index_changed':noindex_rc,'no_index_outside_repo':outside_rc,'no_index_missing_input':error_rc,'no_index_missing_stderr':error_stderr},'behavior_probes':probes,'commands':records}
report=json.loads(json.dumps(report).replace(str(root),'<synthetic-root>'))
(OUT/'report.json').write_text(json.dumps(report,ensure_ascii=True,indent=2)+'\n')
(snap/'manifest.json').write_text(json.dumps({'head':baseline,'git_version':report['git_version'],'scope':'synthetic regular files; nonignored untracked; no concurrent writes; snapshot is evidence, not atomic freeze','files':{p.name:sha(p.read_bytes()) for p in sorted(snap.iterdir()) if p.name!='manifest.json'}},indent=2)+'\n')
print(json.dumps({'three_versions':report['three_versions'],'probe_rc':[p['rc'] for p in probes],'untracked_count':len(untracked),'index_unchanged':True,'worktree_unchanged_during_observation':True,'no_index_missing_rc':error_rc,'no_index_missing_stderr':report['exit_codes']['no_index_missing_stderr']},ensure_ascii=False,indent=2))
9. 完整源码二:独立预期 expected.json
预期值独立写出,不从本轮报告自动生成。退出码负例按本文实测的 Git 2.52.0 固定;其他版本若表现不同,应先检查 stderr 和版本,不能直接改成放宽所有错误。
{
"three_versions": {"HEAD": "result = 1\n", "INDEX": "result = 999\n", "WORKTREE": "result = 1\n"},
"probe_rc": [0, 1],
"untracked": ["-leading.txt", "line\nname.txt", "new file.txt", "space name.txt", "tab\tname.txt"],
"staged_cancel": ["-result = 1", "+result = 999"],
"working_cancel": ["-result = 999", "+result = 1"],
"exit_codes": {"ordinary_diff_changed": 0, "diff_exit_code_changed": 1, "diff_HEAD_cancelled": 0, "no_index_changed": 1, "no_index_outside_repo": 1, "no_index_missing_input": 1}
}
10. 完整源码三:证据校验 verify.py
#!/usr/bin/env python3
"""Independent expected values; read-only verification of one lab run."""
import hashlib, json, pathlib
P=pathlib.Path
expected=json.loads((P(__file__).resolve().parent/'expected.json').read_text())
evidence=P.cwd()/'evidence'
r=json.loads((evidence/'report.json').read_text())
s=evidence/'snapshot'
def sha(b): return hashlib.sha256(b).hexdigest()
assert r['three_versions']==expected['three_versions']
assert [p['rc'] for p in r['behavior_probes']]==expected['probe_rc']
for name,key in [('staged.patch','staged_cancel'),('working.patch','working_cancel')]:
# Match exact patch lines, including the independent expected signs.
lines=(s/name).read_text().splitlines()
assert all(line in lines for line in expected[key])
assert b'MM cancel.py\0' in (s/'status.z').read_bytes()
assert 'cancel.py' not in r['paths']['net_HEAD']
assert sorted((s/'untracked.z').read_bytes().split(b'\0')[:-1])==sorted(p.encode() for p in expected['untracked'])
contents=json.loads((s/'untracked-content.json').read_text())
assert sorted(x['path'] for x in contents)==sorted(expected['untracked'])
for x in contents:
b=x['content_utf8'].encode()
assert x['sha256']==sha(b) and x['bytes']==len(b)
for probe,tree in zip(r['behavior_probes'],['WORKTREE','INDEX']):
source=expected['three_versions'][tree]
assert probe['source']==source
assert probe['source_sha256']==sha(source.encode())
assert probe['assertion']=='assert result == 1\n'
assert probe['executed_code_sha256']==sha((source+probe['assertion']).encode())
assert probe['head']==r['head']
for field,file in [('status_sha256','status.z'),('staged_patch_sha256','staged.patch'),('working_patch_sha256','working.patch')]:
assert probe[field]==sha((s/file).read_bytes())
o=r['observation']
assert o['index_before_sha256']==o['index_after_sha256']
assert o['index_before_mtime_ns']==o['index_after_mtime_ns']
assert o['worktree_before']==o['worktree_after']
for key,value in expected['exit_codes'].items(): assert r['exit_codes'][key]==value
assert 'Could not access' in r['exit_codes']['no_index_missing_stderr']
manifest=json.loads((s/'manifest.json').read_text())
for file,digest in manifest['files'].items(): assert sha((s/file).read_bytes())==digest
assert all(r['assertions'][key] for key in ['external_diff_textconv_fsmonitor_helpers_not_run','same_status_different_patch'])
print('PASS: independent values, two patches, NUL paths, test binding and unchanged observation')
11. 完整源码四:双环境复跑 run_matrix.py
#!/usr/bin/env python3
"""Run in an empty directory. Requires only Python and Git on PATH."""
import json, os, pathlib, subprocess, sys
here=pathlib.Path(__file__).resolve().parent
for label in ['clean','poisoned']:
out=pathlib.Path.cwd()/label
out.mkdir(exist_ok=False)
env={k:v for k,v in os.environ.items() if not k.startswith('GIT_')}
if label=='poisoned':
env.update(GIT_DIR='/nonexistent',GIT_WORK_TREE='/nonexistent',
GIT_INDEX_FILE='/nonexistent',GIT_CONFIG_COUNT='1',
GIT_CONFIG_KEY_0='diff.external',GIT_CONFIG_VALUE_0='/nonexistent',
GIT_EXTERNAL_DIFF='/nonexistent',GIT_TEMPLATE_DIR='/nonexistent',
GIT_PAGER='/nonexistent',GIT_CONFIG_GLOBAL='/nonexistent')
for script in ['lab.py','verify.py']:
subprocess.run([sys.executable,str(here/script)],cwd=out,env=env,check=True)
a=json.loads(pathlib.Path('clean/evidence/report.json').read_text())
b=json.loads(pathlib.Path('poisoned/evidence/report.json').read_text())
def semantic(v):
if isinstance(v,dict):
# Index contains filesystem stat data; independent repos differ here.
# verify.py checks each before/after pair without normalization.
return {k:semantic(x) for k,x in v.items()
if 'mtime' not in k and k not in ['index_before_sha256','index_after_sha256']}
if isinstance(v,list): return [semantic(x) for x in v]
return v
assert semantic(a)==semantic(b)
print('PASS: clean / poisoned semantic reports match; per-run index bytes and mtimes preserved')
12. 补充实验:导出目录里的测试通过了,却少跑了一项
前面的单文件探针回答了源码版本是否一致。继续往前走一步,如果把暂存区导出到独立目录再跑测试,导出过程本身也要验收。这个补充实验于 2026 年 10 月 13 日在 Linux、Git 2.52.0、Python 3.12.14 上运行:暂存树里有两个测试,git archive 导出的目录只剩烟雾测试;内置 unittest discover 跑了一个测试,显示 OK,退出码为 0。被省略的核心测试恰好会发现 VALUE = 999 的错误。
这里的遗漏来自固定夹具中的 export-ignore 属性。它是明确配置的归档行为,拿归档目录作为完整暂存树的替身时,才会影响验收结论。导出目录通过的那项测试确实通过了,问题在于原先约定的另一项测试根本没有进入运行目录。
补充代码只创建自己的临时合成仓库,范围为根目录下五个固定名称、模式为 100644 的普通文件。原文四份脚本和单文件探针保持原样;下面的实验是另一个独立目录中的程序,没有执行 Codex、模型或真实项目。
13. 五份输入,把缺失的测试和保留下来的测试分开看
fixtures.json 给出完整初始内容。value.py 起初是 VALUE = 1,先把 999 暂存,再把工作区改回 1。test_core.py 检查数值等于 1,test_smoke.py 只检查它是整数。这是两项不同的检查:999 能通过类型检查,却不能通过数值检查。固定的 .gitattributes 让归档忽略 test_core.py,并给 version.txt 设置 export-subst。
暂存树的五个路径:
.gitattributes
test_core.py
test_smoke.py
value.py
version.txt
固定 test ID:
test_core.ValueTests.test_value
test_smoke.SmokeTests.test_value_type
程序把合成仓库的 index 复制到另一个临时文件,再为这份副本设置 GIT_INDEX_FILE,调用 write-tree 得到明确的 tree ID。write-tree 会创建 Git 对象,也可能更新所用索引的缓存,因此这里没有在原 index 上执行。随后按这个 tree ID 列出对象,逐项要求固定路径、100644 模式和 blob 类型,再用 cat-file blob 读取原始字节。Git 的说明分别给出了 write-tree 的索引来源及完整合并前提,以及 cat-file 指定类型时返回原始对象内容的行为。
读取器遇到第六个路径、子目录、非预期名称、可执行文件、符号链接或子模块条目就拒绝。这个白名单让实验可复查,也把适用范围限定得很窄。不要删去这些检查后,把它当作任意仓库的索引导出工具。
14. 路径和字节要在导入前核对,测试身份要在运行后核对
gate(reference, candidate) 比较完整路径集合以及同名文件的精确字节,分别报告 missing、extra、changed。reference 来自固定暂存树的原始 blob,candidate 来自归档读取结果。当前归档缺少 test_core.py,因此 guarded_run 在建立运行目录之前就返回 tests_started=false,后续导入不会开始。为展示错误路径,实验另外保留一次明确标注的无门禁归档运行。
归档读取只处理程序自己生成的 tar:在内存里核对固定名称、重复成员、普通文件类型、尺寸上限和执行位,再逐份取内容;没有调用 extract 或 extractall。通过文件门禁后,程序在新目录写入已核对的内容,启动单独的 Python 进程。测试发现会导入测试模块,所以测试 ID 无法靠这里的动态发现机制在导入前检查;运行器会把发现的 ID、发现数、实际运行数、失败、错误、跳过项及 loader 错误记录回来。
suite_accepted 随后要求 ID 与 expected.json 中两个固定名称完全相符,发现数和运行数都为 2,且无失败、错误、跳过、加载错误,退出码为 0。文件门禁和运行结果检查有各自的职责。只比较文件数会漏掉同数量改名,只比较路径会漏掉被改成无条件通过的测试,只比较测试数量也无法确认执行的是哪两项检查。
工作区: 2 tests / 0 failures / rc 0
archive: 1 test / 0 failures / rc 0
原始暂存 blob: 2 tests / 1 failure / rc 1
显式重新暂存后:2 tests / 0 failures / rc 0
archive 文件门禁:missing = ["test_core.py"]
guarded archive:tests_started = false
这里还保留了一个容易混淆的对照:本机内置 python -I -B -m unittest discover 在空目录运行时,记录的是 NO TESTS RAN 和退出码 5。归档的绿色结果来自一项确实运行且通过的烟雾测试,不能把它讲成内置命令“零测试也返回 0”。CPython v3.12.14 的 TestProgram 实现包含无测试退出分支。本文用于生成结构化结果的简化运行器则按 TestResult.wasSuccessful() 选择退出码,所以 suite_accepted 仍明确拒绝空套件;它没有把简化运行器的行为当作内置 CLI 的保证。
15. 同一棵树,归档属性还可能改变文件内容
工作区的 .gitattributes 被故意改成取消 test_core.py 的 export-ignore,但这份变化没有暂存。默认 archive 仍按固定树里的属性省略测试。另一次加入 --worktree-attributes 的归档会保留测试,文件门禁在当前夹具上通过。git-archive 的属性说明解释了默认树内属性、工作区属性选项和 info/attributes 的覆盖入口。这组对照说明,导出记录需要把属性来源也写清楚;仅保存 tree ID 还没有描述完整的归档命令与环境。
version.txt 的原始内容为 build=$Format:%H$。把 bare tree ID 交给 archive 时,这段占位符原样保留。程序再用 commit-tree 创建一个仅供实验的提交对象,核对该对象的 ^{tree} 正是同一 tree ID,再归档这个提交对象;此时占位符被替换成这个提交的哈希,门禁在 missing 之外还报告 changed=["version.txt"]。这个临时提交对象没有更新 HEAD。
gitattributes 的 export-subst 说明将替换与提交信息是否可用联系起来,并区分 tree 与 commit/tag 输入。本次程序实际比较的是 bare tree 与 commit;tag 行为只援引官方说明,没有把未执行的 tag 分支写成实测。用占位符变化推断两个归档来自不同源码树,在这个例子中就会得出错误结论。
16. 恢复时,明确改变哪一个候选版本
发现暂存值仍为 999 后,程序只在自己的临时合成仓库执行 git add -- value.py。此时工作区的值已是 1,所以这一步明确改变了索引候选内容。随后重新复制 index、生成新的 tree ID、读取五份原始 blob,并用新树自己的内容作为门禁基准,运行相同的两个测试。结果为两项通过,退出码 0;.gitattributes 的工作区改动没有被这次单路径暂存带入。
重新暂存之前,报告检查了原 index 字节、index mtime、HEAD,以及工作区 value.py 的内容均与观察起点一致。不要把这四项检查扩写成对整个工作区每个文件或其 mtime 的承诺。write-tree 和 commit-tree 仍会在临时仓库对象库中写入对象;这里的“不变”只指列明的观察对象和阶段。
实际项目要不要重新暂存,由要提交的内容决定。若测试文件、依赖配置或其他未暂存修改也影响结果,单独暂存 value.py 未必够用。先说明允许的仓库、路径、运行环境与提交意图,再决定后续动作;这份程序没有已有仓库入口,也不会代替读者对真实改动做这个决定。
17. 在新的空目录复跑,并保留失败发生的位置
补充部分共五份文件:archive_lab.py、fixtures.json、expected.json、test_archive_lab.py、test_mutants.py,完整内容都在下文。把它们放在一个新目录,使用可信的 Git 和 Python 3;实测环境为 Git 2.52.0、Python 3.12.14。脚本不安装依赖,不联网。下面按源码包的目录结构运行;若手动保存下文代码,则在保存目录直接执行后三行。
cd source/supplement
python -B archive_lab.py evidence-new
python -B -m unittest -v test_archive_lab
python -B test_mutants.py
evidence-new 必须不存在,避免覆盖上一轮证据。它会保存 report.json、原始暂存输入 JSON、归档 tar、归档 CLI 的 stdout/stderr,以及空套件 CLI 的 stderr。stdout 中的四组摘要应与上文一致。测试中的预期失败是“原始暂存版本的一项数值断言失败”;实验主程序只有核对完这些独立预期后才成功退出。不要用 || true 把实验自身的异常也当作这个预期失败。
expected.json 的路径、ID、数值和各阶段结果是固定预期,不从本轮报告生成。test_archive_lab.py 用独立小夹具检查门禁,包括文件遗漏、额外文件、同数量改名、同路径改写、占位符替换、空测试、同数量错误 ID、加载错误和失败/错误/跳过/退出码。test_mutants.py 先要求原函数通过固定判定,再换入八个故意错误的实现,分别证明指定反例能检出“全部放行”“只数文件”“只看路径”“忽略额外文件”“只看退出码”“只数测试”“忽略跳过”“忽略加载错误”。它们是定向退化检查,不能据此推断任意实现错误都会被发现。
Git 子命令出错或超时,程序立即抛错;输出目录已存在、JSON 不合法、归档成员越界、固定预期不符也会停止。程序不会自行放宽路径集合或更新预期。先查看失败阶段、stderr 和版本,再决定是环境差异还是夹具被改动。内置 CLI 的空测试退出码作为本次环境的固定对照,换环境出现不同结果时应保留记录,不能无声改成“任意非零都算通过”。请勿使用 Python -O;这里的验收断言需要正常执行。
隔离的 HOME、Git 配置、属性来源及空钩子目录只服务于已知合成输入;读取的 tar 也是本程序自己生成的。未验证已有仓库、嵌套目录、符号链接、子模块、可执行文件、冲突索引、稀疏索引、自定义过滤器、并发改动、恶意测试代码或操作系统沙箱。本例也没有形成提交事务,不能保证检查完成后其他进程不会继续改写真实项目。
18. 补充完整源码一:固定夹具 fixtures.json
这五份 UTF-8 文本是实验的全部仓库输入;测试源码也在其中。
{
".gitattributes": "test_core.py export-ignore\nversion.txt export-subst\n",
"value.py": "VALUE = 1\n",
"test_core.py": "import unittest\nfrom value import VALUE\n\nclass ValueTests(unittest.TestCase):\n def test_value(self):\n self.assertEqual(VALUE, 1)\n",
"version.txt": "build=$Format:%H$\n",
"test_smoke.py": "import unittest\nfrom value import VALUE\n\nclass SmokeTests(unittest.TestCase):\n def test_value_type(self):\n self.assertIsInstance(VALUE, int)\n"
}
19. 补充完整源码二:独立预期 expected.json
预期明确区分归档里剩下的一项测试与完整树中的两项测试。
{
"tree_paths": [
".gitattributes",
"test_core.py",
"test_smoke.py",
"value.py",
"version.txt"
],
"archive_paths": [
".gitattributes",
"test_smoke.py",
"value.py",
"version.txt"
],
"index_value": "VALUE = 999\n",
"working_value": "VALUE = 1\n",
"source_version": "build=$Format:%H$\n",
"test_ids": [
"test_core.ValueTests.test_value",
"test_smoke.SmokeTests.test_value_type"
],
"working": {
"tests_run": 2,
"failures": 0,
"errors": 0,
"skipped": 0,
"rc": 0
},
"archive": {
"tests_run": 1,
"failures": 0,
"errors": 0,
"skipped": 0,
"rc": 0
},
"raw_index": {
"tests_run": 2,
"failures": 1,
"errors": 0,
"skipped": 0,
"rc": 1
},
"repaired_index": {
"tests_run": 2,
"failures": 0,
"errors": 0,
"skipped": 0,
"rc": 0
},
"archive_test_ids": [
"test_smoke.SmokeTests.test_value_type"
],
"naive_cli_rc": 0,
"zero_cli_rc": 5,
"cli": {
"working": {
"rc": 0,
"ran": 2,
"status": "OK"
},
"archive": {
"rc": 0,
"ran": 1,
"status": "OK"
},
"raw_index": {
"rc": 1,
"ran": 2,
"status": "FAILED (failures=1)"
},
"repaired_index": {
"rc": 0,
"ran": 2,
"status": "OK"
}
}
}
20. 补充完整源码三:导出与运行 archive_lab.py
"""Only this five-file, trusted synthetic Git fixture; not a repository exporter."""
import hashlib
import io
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile
HERE = Path(__file__).resolve().parent
NAMES = {'.gitattributes', 'test_core.py', 'test_smoke.py', 'value.py', 'version.txt'}
GIT = shutil.which('git', path='/usr/local/bin:/usr/bin:/bin')
RUNNER = '''import json, pathlib, sys, unittest
sys.path.insert(0, str(pathlib.Path.cwd()))
loader = unittest.TestLoader()
suite = loader.discover('.', pattern='test*.py')
def ids(s):
if isinstance(s, unittest.TestSuite):
return [name for t in s for name in ids(t)]
return [s.id()]
test_ids = ids(suite)
count = suite.countTestCases()
r = unittest.TestResult()
suite.run(r)
print(json.dumps(dict(ids=test_ids, discovered=count, tests_run=r.testsRun,
failures=len(r.failures), errors=len(r.errors), skipped=len(r.skipped),
loader_errors=loader.errors), sort_keys=True))
sys.exit(0 if r.wasSuccessful() else 1)
'''
def sha(raw):
return hashlib.sha256(raw).hexdigest()
def gate(reference, candidate):
"""Exact filenames and raw blob contents, not counts alone."""
missing = sorted(reference.keys() - candidate.keys())
extra = sorted(candidate.keys() - reference.keys())
changed = sorted(p for p in reference.keys() & candidate.keys()
if reference[p] != candidate[p])
return dict(accepted=not (missing or extra or changed), missing=missing,
extra=extra, changed=changed)
def suite_accepted(result, expected_ids):
"""Trusted fixed fixture contract. Counts alone cannot name a suite."""
return (sorted(result['ids']) == sorted(expected_ids)
and result['discovered'] == len(expected_ids)
and result['tests_run'] == len(expected_ids)
and len(expected_ids) > 0
and all(result[k] == 0 for k in ('failures', 'errors', 'skipped', 'rc'))
and not result['loader_errors'])
def guarded_run(path, reference, candidate, env, expected_ids):
check = gate(reference, candidate)
if not check['accepted']:
return dict(accepted=False, tests_started=False, export_gate=check)
materialize(path, candidate)
run = test_run(path, env)
return dict(accepted=suite_accepted(run, expected_ids), tests_started=True,
export_gate=check, run=run)
def read_fixed_tar(raw):
"""Read our own archive in memory. Never call extract/extractall."""
result = {}
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as archive:
members = archive.getmembers()
if len(members) > len(NAMES):
raise ValueError('outside fixed fixture')
for item in members:
if (item.name not in NAMES or item.name in result or not item.isfile()
or item.size > 65536 or item.mode & 0o111):
raise ValueError('outside fixed regular-file fixture')
result[item.name] = archive.extractfile(item).read()
return result
def materialize(path, files):
if not files.keys() <= NAMES:
raise ValueError('outside fixed names')
path.mkdir(exist_ok=False)
for name, raw in files.items():
(path / name).write_bytes(raw)
def test_run(path, env):
command = [sys.executable, '-I', '-B', '-c', RUNNER]
r = subprocess.run(command, cwd=path, env=env, capture_output=True, timeout=15)
result = json.loads(r.stdout)
result.update(rc=r.returncode, stderr=r.stderr.decode())
return result
def main(out):
out.mkdir(exist_ok=False)
fixtures = {k: v.encode() for k, v in json.loads((HERE/'fixtures.json').read_text()).items()}
expected = json.loads((HERE/'expected.json').read_text())
if set(fixtures) != NAMES:
raise ValueError('fixture names changed')
with tempfile.TemporaryDirectory(prefix='index-archive-') as temp:
root = Path(temp)
repo, home, hooks = root/'repo', root/'home', root/'hooks'
home.mkdir(); hooks.mkdir(); materialize(repo, fixtures)
env = dict(PATH='/usr/local/bin:/usr/bin:/bin', HOME=str(home),
XDG_CONFIG_HOME=str(home), LC_ALL='C', GIT_CONFIG_NOSYSTEM='1',
GIT_CONFIG_GLOBAL=os.devnull, GIT_ATTR_NOSYSTEM='1',
GIT_AUTHOR_NAME='Fixture Author', GIT_AUTHOR_EMAIL='fixture@example.invalid',
GIT_COMMITTER_NAME='Fixture Author', GIT_COMMITTER_EMAIL='fixture@example.invalid',
GIT_AUTHOR_DATE='2026-01-01T00:00:00Z',
GIT_COMMITTER_DATE='2026-01-01T00:00:00Z')
prefix = [GIT, '--no-pager', '--no-optional-locks', '-C', str(repo),
'-c', f'core.hooksPath={hooks}', '-c', 'core.fsmonitor=false',
'-c', f'core.attributesFile={os.devnull}', '-c', 'core.autocrlf=false',
'-c', 'commit.gpgsign=false']
commands = []
def git(*args, index=None):
local_env = dict(env)
if index is not None:
local_env['GIT_INDEX_FILE'] = str(index)
r = subprocess.run(prefix+list(args), env=local_env, capture_output=True, timeout=15)
commands.append(dict(argv=list(args), rc=r.returncode,
stderr=r.stderr.decode(), stdout_sha256=sha(r.stdout)))
if r.returncode:
raise RuntimeError(commands[-1])
return r.stdout
git('init', '-b', 'main', '--template=')
git('add', '--', *sorted(NAMES))
git('commit', '-m', 'five trusted files')
head = git('rev-parse', 'HEAD').decode().strip()
(repo/'value.py').write_bytes(b'VALUE = 999\n')
git('add', '--', 'value.py')
(repo/'value.py').write_bytes(b'VALUE = 1\n')
# Worktree attribute differs. Default archive must still read pinned tree attrs.
(repo/'.gitattributes').write_bytes(b'# working-only override\ntest_core.py -export-ignore\n')
index_path = repo/'.git/index'
index_before = index_path.read_bytes()
index_mtime = index_path.stat().st_mtime_ns
def freeze(label):
copy = root/(label+'-index')
copy.write_bytes(index_path.read_bytes())
# write-tree creates tree objects and may update this copied index's cache.
return git('write-tree', index=copy).decode().strip()
def tree_files(tree):
listing = git('ls-tree', '-r', '-z', '--full-tree', tree)
entries, files = [], {}
for record in listing.split(b'\0')[:-1]:
metadata, path = record.split(b'\t', 1)
mode, kind, oid = metadata.decode().split()
name = path.decode('ascii')
if name not in NAMES or mode != '100644' or kind != 'blob':
raise ValueError('outside fixed tree')
raw = git('cat-file', 'blob', oid)
files[name] = raw
entries.append(dict(path=name, mode=mode, kind=kind, oid=oid,
size=len(raw), sha256=sha(raw)))
if set(files) != NAMES:
raise ValueError('tree is not complete fixture')
return entries, files
tree = freeze('staged')
entries, raw_files = tree_files(tree)
archive_bytes = git('archive', '--format=tar', tree)
archive_files = read_fixed_tar(archive_bytes)
(out/'staged.tar').write_bytes(archive_bytes)
# This materialization intentionally illustrates the naïve archive path.
# The input is generated here, and the reader allows only the five fixed names.
naive = root/'naive'
materialize(naive, archive_files)
raw_dir = root/'raw-index'; materialize(raw_dir, raw_files)
runs = dict(working=test_run(repo, env), archive=test_run(naive, env),
raw_index=test_run(raw_dir, env))
cli = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
cwd=naive, env=env, capture_output=True, timeout=15)
(out/'naive-unittest.stdout').write_bytes(cli.stdout)
(out/'naive-unittest.stderr').write_bytes(cli.stderr)
empty_dir = root/'empty-suite'; empty_dir.mkdir()
zero_cli = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
cwd=empty_dir, env=env, capture_output=True, timeout=15)
(out/'zero-unittest.stderr').write_bytes(zero_cli.stderr)
assert zero_cli.returncode == expected['zero_cli_rc']
assert b'Ran 0 tests' in zero_cli.stderr and b'NO TESTS RAN' in zero_cli.stderr
# Attribute-source probe: same tree/content, only attribute lookup changes.
override_files = read_fixed_tar(git('archive', '--format=tar', '--worktree-attributes', tree))
# Both object types name exactly the same tree. Only commit has substitution context.
commit = git('commit-tree', tree, '-m', 'synthetic attribute probe').decode().strip()
assert git('rev-parse', commit+'^{tree}').decode().strip() == tree
commit_files = read_fixed_tar(git('archive', '--format=tar', commit))
gates = dict(archive=gate(raw_files, archive_files),
worktree_attributes=gate(raw_files, override_files),
raw_index=gate(raw_files, raw_files),
commit_archive=gate(raw_files, commit_files))
guarded = dict(archive=guarded_run(root/'blocked-archive', raw_files, archive_files,
env, expected['test_ids']),
raw_index=guarded_run(root/'checked-index', raw_files, raw_files,
env, expected['test_ids']))
assert not (root/'blocked-archive').exists()
unchanged = dict(index_bytes=index_before == index_path.read_bytes(),
index_mtime=index_mtime == index_path.stat().st_mtime_ns,
head=head == git('rev-parse', 'HEAD').decode().strip(),
working_value=(repo/'value.py').read_bytes() == b'VALUE = 1\n')
assert all(unchanged.values())
# Recovery in this disposable fixture: explicitly stage just value.py.
# Do not use this step in a user's repository without scope/approval.
git('add', '--', 'value.py')
repaired_tree = freeze('repaired')
repaired_entries, repaired_files = tree_files(repaired_tree)
repaired_dir = root/'repaired'; materialize(repaired_dir, repaired_files)
runs['repaired_index'] = test_run(repaired_dir, env)
cli_runs = {}
for label, directory in [('working', repo), ('archive', naive),
('raw_index', raw_dir), ('repaired_index', repaired_dir)]:
r = subprocess.run([sys.executable, '-I', '-B', '-m', 'unittest', 'discover'],
cwd=directory, env=env, capture_output=True, timeout=15)
oracle = expected['cli'][label]
assert r.returncode == oracle['rc']
assert ('Ran '+str(oracle['ran'])+' test').encode() in r.stderr
assert oracle['status'].encode() in r.stderr
(out/('cli-'+label+'.stdout')).write_bytes(r.stdout)
(out/('cli-'+label+'.stderr')).write_bytes(r.stderr)
cli_runs[label] = dict(rc=r.returncode, stdout=r.stdout.decode(),
stderr=r.stderr.decode())
guarded['repaired_index'] = guarded_run(root/'checked-repaired', repaired_files,
repaired_files, env, expected['test_ids'])
for label in ('working', 'archive', 'raw_index', 'repaired_index'):
for field, value in expected[label].items():
assert runs[label][field] == value, (label, field, runs[label])
for label in ('working', 'raw_index', 'repaired_index'):
assert runs[label]['ids'] == expected['test_ids']
assert runs[label]['discovered'] == len(expected['test_ids'])
assert runs['archive']['ids'] == expected['archive_test_ids']
assert runs['archive']['discovered'] == len(expected['archive_test_ids'])
assert cli.returncode == expected['naive_cli_rc']
assert b'Ran 1 test' in cli.stderr and b'OK' in cli.stderr
assert sorted(raw_files) == expected['tree_paths']
assert sorted(archive_files) == expected['archive_paths']
assert raw_files['value.py'].decode() == expected['index_value']
assert raw_files['version.txt'].decode() == expected['source_version']
assert archive_files['version.txt'] == raw_files['version.txt']
assert commit_files['version.txt'] == ('build='+commit+'\n').encode()
assert gates['archive'] == dict(accepted=False, missing=['test_core.py'], extra=[], changed=[])
assert gates['worktree_attributes']['accepted']
report = dict(git_version=git('--version').decode().strip(),
python=platform.python_version(), tree=tree, commit=commit,
repaired_tree=repaired_tree, tree_entries=entries,
repaired_entries=repaired_entries, runs=runs, cli_runs=cli_runs, gates=gates, guarded=guarded,
unchanged_before_explicit_repair=unchanged,
archive_paths=sorted(archive_files),
tree_attributes=raw_files['.gitattributes'].decode(),
working_attributes=(repo/'.gitattributes').read_text(),
substitution=dict(raw=raw_files['version.txt'].decode(),
bare_tree=archive_files['version.txt'].decode(),
commit=commit_files['version.txt'].decode()),
naive_cli_rc=cli.returncode, zero_cli_rc=zero_cli.returncode, commands=commands)
report_text = json.dumps(report, indent=2).replace(str(root), '<synthetic-root>')
(out/'report.json').write_text(report_text+'\n')
(out/'raw-index-inputs.json').write_text(json.dumps({k:v.decode() for k,v in raw_files.items()}, indent=2)+'\n')
summary = {label:{k:run[k] for k in ('discovered','tests_run','failures','errors','rc')}
for label,run in runs.items()}
summary.update(archive_gate=gates['archive'], commit_archive_gate=gates['commit_archive'],
index_unchanged_before_repair=all(unchanged.values()))
print(json.dumps(summary, indent=2))
return 0
if __name__ == '__main__':
if len(sys.argv) != 2:
raise SystemExit('usage: python archive_lab.py NEW_OUTPUT_DIRECTORY')
raise SystemExit(main(Path(sys.argv[1]).resolve()))
21. 补充完整源码四:门禁反例 test_archive_lab.py
"""Independent names, contents and test IDs; no expected values from live output."""
import copy
import tempfile
from pathlib import Path
import unittest
from archive_lab import gate, guarded_run, suite_accepted
class ExportGateTests(unittest.TestCase):
def setUp(self):
self.reference = {'.gitattributes': b'test_core.py export-ignore\n',
'value.py': b'VALUE = 999\n',
'test_core.py': b'assert VALUE == 1\n'}
def test_exact_blob_set(self):
self.assertEqual(gate(self.reference, dict(self.reference)),
{'accepted': True, 'missing': [], 'extra': [], 'changed': []})
def test_omitted_test(self):
candidate = dict(self.reference); del candidate['test_core.py']
self.assertEqual(gate(self.reference, candidate),
{'accepted': False, 'missing': ['test_core.py'], 'extra': [], 'changed': []})
def test_same_count_different_file(self):
candidate = dict(self.reference)
candidate['test_decoy.py'] = candidate.pop('test_core.py')
self.assertEqual(len(candidate), len(self.reference))
self.assertEqual(gate(self.reference, candidate),
{'accepted': False, 'missing': ['test_core.py'],
'extra': ['test_decoy.py'], 'changed': []})
def test_extra_file(self):
candidate = dict(self.reference, **{'unexpected.txt': b'not in tree\n'})
self.assertEqual(gate(self.reference, candidate),
{'accepted': False, 'missing': [],
'extra': ['unexpected.txt'], 'changed': []})
def test_same_paths_changed_test(self):
candidate = dict(self.reference); candidate['test_core.py'] = b'assert True\n'
self.assertEqual(set(candidate), set(self.reference))
self.assertEqual(gate(self.reference, candidate)['changed'], ['test_core.py'])
self.assertFalse(gate(self.reference, candidate)['accepted'])
def test_missing_test_blocks_before_directory_or_import(self):
candidate = dict(self.reference); del candidate['test_core.py']
with tempfile.TemporaryDirectory() as d:
out = Path(d)/'must-not-exist'
result = guarded_run(out, self.reference, candidate, {}, ['test_core.Case.test_value'])
self.assertFalse(result['tests_started'])
self.assertFalse(out.exists())
def test_substituted_blob(self):
self.assertEqual(gate({'version.txt': b'build=$Format:%H$\n'},
{'version.txt': b'build=0123456789\n'})['changed'], ['version.txt'])
class SuiteGateTests(unittest.TestCase):
def setUp(self):
self.ids = ['test_core.ValueTests.test_value']
self.good = dict(ids=self.ids, discovered=1, tests_run=1, failures=0,
errors=0, skipped=0, rc=0, loader_errors=[])
def test_exact_success(self):
self.assertTrue(suite_accepted(self.good, self.ids))
def test_zero_tests_with_rc_zero_is_rejected(self):
result = dict(self.good, ids=[], discovered=0, tests_run=0)
self.assertFalse(suite_accepted(result, self.ids))
def test_same_count_different_test_is_rejected(self):
result = dict(self.good, ids=['test_decoy.OtherTests.test_trivial'])
self.assertFalse(suite_accepted(result, self.ids))
def test_failure_error_skip_or_wrong_exit_is_rejected(self):
for name in ['failures', 'errors', 'skipped', 'rc']:
with self.subTest(name=name):
result = dict(self.good, **{name: 1})
self.assertFalse(suite_accepted(result, self.ids))
def test_loader_errors_are_rejected(self):
result = dict(self.good, loader_errors=['synthetic import failure'])
self.assertFalse(suite_accepted(result, self.ids))
def test_expected_empty_suite_is_not_acceptance(self):
result = dict(self.good, ids=[], discovered=0, tests_run=0)
self.assertFalse(suite_accepted(result, []))
if __name__ == '__main__':
unittest.main()
22. 补充完整源码五:定向变异 test_mutants.py
"""Fixed local gate oracles, then deliberately wrong implementations."""
import json
from archive_lab import gate, suite_accepted
REF = {'test_core.py': b'core assertion\n', 'test_smoke.py': b'smoke assertion\n'}
IDS = ['test_core.ValueTests.test_value', 'test_smoke.SmokeTests.test_value_type']
GOOD = dict(ids=IDS, discovered=2, tests_run=2, failures=0, errors=0,
skipped=0, rc=0, loader_errors=[])
FILE_CASES = [
('exact', dict(REF), True),
('missing', {'test_smoke.py': REF['test_smoke.py']}, False),
('renamed_same_count', {'test_other.py': REF['test_core.py'],
'test_smoke.py': REF['test_smoke.py']}, False),
('changed', dict(REF, **{'test_core.py': b'pass\n'}), False),
('extra', dict(REF, **{'test_extra.py': b'pass\n'}), False),
]
RUN_CASES = [
('exact', dict(GOOD), True),
('empty', dict(GOOD, ids=[], discovered=0, tests_run=0), False),
('wrong_ids_same_count', dict(GOOD, ids=['other.A.test_a', 'other.A.test_b']), False),
('failed', dict(GOOD, failures=1, rc=1), False),
('skipped', dict(GOOD, skipped=1), False),
('loader_error', dict(GOOD, loader_errors=['fixture import failure']), False),
]
def file_failures(fn):
return [name for name, files, wanted in FILE_CASES
if bool(fn(REF, files)) != wanted]
def run_failures(fn):
return [name for name, result, wanted in RUN_CASES
if bool(fn(result, IDS)) != wanted]
assert file_failures(lambda r, c: gate(r, c)['accepted']) == []
assert run_failures(suite_accepted) == []
# Each target must fail on its named fixed oracle, not on a crash.
mutants = [
('accept_every_export', 'missing', file_failures(lambda r, c: True)),
('file_count_only', 'renamed_same_count', file_failures(lambda r, c: len(r) == len(c))),
('paths_only', 'changed', file_failures(lambda r, c: r.keys() == c.keys())),
('ignore_extra', 'extra', file_failures(lambda r, c: all(c.get(k) == v for k, v in r.items()))),
('exit_only', 'empty', run_failures(lambda r, ids: r['rc'] == 0)),
('count_without_ids', 'wrong_ids_same_count', run_failures(lambda r, ids:
r['tests_run'] == len(ids) and r['rc'] == 0)),
('ignore_skips', 'skipped', run_failures(lambda r, ids:
suite_accepted(dict(r, skipped=0), ids))),
('ignore_loader_error', 'loader_error', run_failures(lambda r, ids:
suite_accepted(dict(r, loader_errors=[]), ids))),
]
for name, required, failures in mutants:
assert required in failures, (name, required, failures)
print(json.dumps({'baseline': 'PASS', 'killed': [dict(mutant=n, required_case=c,
failed_cases=f) for n, c, f in mutants]}, indent=2))
23. 补充材料:完整离线包与源码包
下载的 ZIP 现在包含 fullarticle-offline.html 完整离线文章、四幅内嵌图及 figures/ 下独立 SVG、原文和补充实验的九份源码、固定夹具和预期、历史样例输出及 SHA256 清单。解压后打开 fullarticle-offline.html 即可离线阅读;官方参考链接需要联网后打开。
source/original 保留原文四份文件;source/supplement 为补充实验五份文件;source/sample-output 保留 2026 年 10 月 13 日的样例结果。复跑时请使用新输出目录。2026 年 10 月 20 日补齐了旧 ZIP 缺失的离线文章和四幅 SVG,并重新核对完整文件清单;没有把历史样例改写成这次复跑结果。
SHA256 清单用于发现文件变化,不是签名,也不能证明输出不可伪造。程序、输入、预期和报告都由同一人改写时,仍可生成表面自洽的结果。要在工程验收中采用类似方式,还需要独立确定测试范围、保护证据来源并复核要提交的版本。