20261016 修复 Manifest failed-fetch 缓存与子树恢复
This commit is contained in:
72
tests/support/test_failed_fetch.py
Normal file
72
tests/support/test_failed_fetch.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Real CLI restarts: failed-fetch must retain valid per-instance results."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_public_config import make_rsync_wrapper, read_json, run_checked, write_config
|
||||
|
||||
|
||||
def run(binary: str, evidence: Path | None = None, expect_no_cache: bool = False) -> dict:
|
||||
proof = {'runs': []}
|
||||
with tempfile.TemporaryDirectory(prefix='panda-failed-fetch-') as temp:
|
||||
base = Path(temp)
|
||||
fixture = base / 'fixture'
|
||||
run_checked([sys.executable, str(Path(__file__).with_name('generate_repository.py')),
|
||||
'--output', str(fixture), '--rsync-host', '127.0.0.1', '--strict-names'])
|
||||
repository = base / 'repository'
|
||||
shutil.copytree(fixture / 'cases/baseline-v1/repository', repository)
|
||||
wrapper = base / 'rsync.py'
|
||||
log = base / 'rsync.log'
|
||||
make_rsync_wrapper(wrapper, fixture, log)
|
||||
wrapper.write_text(wrapper.read_text().replace(str(fixture / 'cases/baseline-v1/repository'), str(repository)))
|
||||
wrapper.chmod(0o755)
|
||||
baseline = None
|
||||
for name in ('initial', 'missing-object', 'missing-parent', 'recovered'):
|
||||
if name == 'missing-object':
|
||||
(repository / 'child/valid.roa').write_bytes(b'corrupted downloaded ROA')
|
||||
if name == 'missing-parent':
|
||||
(repository / 'root/root.mft').write_bytes(b'corrupted downloaded manifest')
|
||||
if name == 'recovered':
|
||||
shutil.rmtree(repository)
|
||||
shutil.copytree(fixture / 'cases/baseline-v2/repository', repository)
|
||||
out = base / name
|
||||
config = base / 'validate.toml'
|
||||
write_config(config, fixture=fixture, output=out, state=base / 'state', ccr=out / 'result.ccr',
|
||||
wrapper=wrapper, mirror=base / 'mirror', sync_preference='rsync-only', rrdp_mode='auto',
|
||||
validation_time='2026-06-01T00:00:00Z', resource_mode='rfc6487', scope='publication-point')
|
||||
start = len(log.read_text().splitlines()) if log.exists() else 0
|
||||
result = subprocess.run([binary, 'validate', '--config', str(config)], capture_output=True, text=True, timeout=120)
|
||||
summary = read_json(out / 'summary.json')
|
||||
expected = 2 if name == 'recovered' else (0 if expect_no_cache and name.startswith('missing') else 1)
|
||||
assert summary['vrps'] == expected, (name, summary, result.stderr[-4000:])
|
||||
if name.startswith('missing'):
|
||||
assert summary['resultStatus'] == 'partial', summary
|
||||
assert result.returncode == 3, result
|
||||
else:
|
||||
assert summary['resultStatus'] == 'success' and result.returncode == 0, (summary, result.stderr[-3000:])
|
||||
csv = (out / 'vrps.csv').read_bytes()
|
||||
if name == 'initial':
|
||||
baseline = csv
|
||||
elif name.startswith('missing') and not expect_no_cache:
|
||||
assert baseline == csv, 'failed-fetch changed still-valid payloads'
|
||||
requests = log.read_text().splitlines()[start:]
|
||||
if name == 'missing-parent':
|
||||
assert len(requests) == 1, ('cached subtree must not fetch children', requests)
|
||||
proof['runs'].append({'name': name, 'status': summary['resultStatus'], 'vrps': summary['vrps'],
|
||||
'exit': result.returncode, 'rsync_requests': requests})
|
||||
if evidence:
|
||||
evidence.write_text(json.dumps(proof, indent=2) + '\n')
|
||||
return proof
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('binary')
|
||||
parser.add_argument('--evidence', type=Path)
|
||||
parser.add_argument('--expect-no-cache', action='store_true')
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(run(args.binary, args.evidence, args.expect_no_cache)))
|
||||
Reference in New Issue
Block a user