evalstone/bash/test_e2e.py
sora 2ee0af0728 docs: add resource prep section and sync docker/bash helpers
- Rewrite myread.md with proper markdown and four download sources:
  code, data, evalscope-complete-py312 image, execution images.
- Add bash/collect_results.py, perf_backup.py, pull_swe_bench_images.py
  for result aggregation, breakpoint perf recovery, and SWE-bench
  image pre-pulling.
- Update bash/run.py with multi-run suites, perf backup/restore, and
  whitelist-based summary.
- Update config/dpv4-int8_nothinking.yaml benchmark parameters.
- Ignore /docker_images and /results in .gitignore.
2026-07-23 02:18:15 +00:00

180 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""End-to-end offline test for bash/run.py + collect_results + perf_backup.
This script does NOT call any model API. It uses an existing benchmark
directory (aime24/seed_42) and walks through every code path that the real
``run.py`` would touch:
1. backup_perf_stats — confirm a perf summary snapshot is written
2. archive_predictions — confirm per-sample records are deduplicated into
the archive
3. restore_from_backup — confirm a fresh work_dir can be rehydrated from
the backup files
4. collect_results aggregation — confirm correct metrics are produced
5. Whitelist mode — confirm only the requested benchmarks appear in the
summary
6. hle_low alias — confirm hle_low directory maps to canonical "hle"
7. Reset recovery — confirm that even after wiping the predictions file
and the report summary, the archive+backup still recover the metrics
Run from the project root:
python bash/test_e2e.py
"""
import json
import shutil
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import collect_results as cr # noqa: E402
import perf_backup as pb # noqa: E402
PROJECT_ROOT = Path('/data1/sora/evalscope')
SOURCE_OUTPUT = PROJECT_ROOT / 'output'
def banner(msg):
print('\n' + '=' * 60)
print(f' {msg}')
print('=' * 60)
def assert_close(actual, expected, name, tol=1e-3):
if abs(actual - expected) > tol:
raise AssertionError(f'{name}: expected ~{expected}, got {actual}')
print(f' OK {name}={actual:.4f}')
def main():
banner('Setting up isolated test output dir')
test_root = PROJECT_ROOT / 'output_e2e_test'
if test_root.exists():
shutil.rmtree(test_root)
test_root.mkdir(parents=True)
# Copy the canonical aime24 data into the isolated dir
src = SOURCE_OUTPUT / 'aime24' / 'seed_42'
dst = test_root / 'aime24' / 'seed_42'
shutil.copytree(src, dst)
# And copy hle_low as hle_low under the test dir (so we exercise the
# alias code path).
hle_src = SOURCE_OUTPUT / 'hle_low' / 'seed_42'
hle_dst = test_root / 'hle_low' / 'seed_42'
if hle_src.exists():
shutil.copytree(hle_src, hle_dst)
benchmark = 'aime24'
model_name = 'DeepSeek-V4-Flash-Int8'
banner('1) backup_perf_stats — snapshot summary')
report_json = dst / 'reports' / model_name / f'{benchmark}.json'
backup_path = pb.backup_perf_stats(test_root, benchmark, model_name, report_json)
assert backup_path.exists(), 'backup file was not created'
payload = json.loads(backup_path.read_text())
assert payload['n_samples'] == 30, f'expected 30 samples, got {payload["n_samples"]}'
print(f' OK backup written: {backup_path}, n_samples={payload["n_samples"]}')
banner('2) archive_predictions — deduplicate into archive')
preds = dst / 'predictions' / model_name
archive_path = pb.archive_predictions(test_root, benchmark, model_name, preds)
assert archive_path.exists(), 'archive file was not created'
n_lines = sum(1 for line in archive_path.read_text().splitlines() if line.strip())
assert n_lines == 30, f'expected 30 archived samples, got {n_lines}'
print(f' OK archive written: {archive_path}, samples={n_lines}')
banner('3) restore_from_backup — rehydrate a fresh work_dir')
fresh_dir = test_root / 'fresh_work' / 'seed_42'
fresh_report = fresh_dir / 'reports' / model_name / f'{benchmark}.json'
fresh_preds = fresh_dir / 'predictions' / model_name
# Pre-create empty structure to mimic what eval_task would do
fresh_report.parent.mkdir(parents=True, exist_ok=True)
fresh_report.write_text(json.dumps({
'name': f'{model_name}@{benchmark}',
'perf_metrics': {'summary': {'n_samples': 1}},
}))
restored = pb.restore_from_backup(
test_root, benchmark, model_name, fresh_preds, fresh_report,
)
assert restored, 'restore_from_backup returned False'
restored_report = json.loads(fresh_report.read_text())
assert restored_report['perf_metrics']['summary']['n_samples'] == 30, \
f'restored n_samples={restored_report["perf_metrics"]["summary"]["n_samples"]}'
print(' OK restored report n_samples=30 (matches backup)')
banner('4) collect_results — aggregate metrics')
csv, xlsx = cr.eval_benchmark([benchmark], test_root, model_name)
import pandas as pd
df = pd.read_csv(csv)
aime24 = df[df['Benchmark'] == 'aime24'].iloc[0]
# Single seed_42 run, so values match the report directly (no multi-run
# averaging). The full-summary CSV averages across run_1..run_11.
assert_close(aime24['得分'], 0.6333, 'aime24 score (single seed)')
assert_close(aime24['实测时间(h)'], 0.1469, 'aime24 duration (h)')
assert_close(aime24['总样本数'], 30, 'aime24 sample count')
assert_close(aime24['延迟_mean(s)'], 35.24971, 'aime24 latency mean')
assert_close(aime24['TTFT_mean(s)'], 0.26301, 'aime24 TTFT mean')
assert_close(aime24['TPOT_mean(s)'], 0.02165, 'aime24 TPOT mean')
assert_close(aime24['输入tokens_mean'], 119.33, 'aime24 input_tokens_mean')
assert_close(aime24['输出tokens_mean'], 1609.8, 'aime24 output_tokens_mean')
banner('5) Whitelist mode — only the requested benchmarks appear')
csv2, _ = cr.eval_benchmark([benchmark], test_root, model_name)
df2 = pd.read_csv(csv2)
assert df2['Benchmark'].dropna().tolist() == ['aime24'], \
f'whitelist did not limit benchmarks: {df2["Benchmark"].tolist()}'
print(' OK summary only contains aime24 + total')
banner('6) hle_low → hle alias')
if hle_src.exists():
csv3, _ = cr.eval_benchmark(['hle'], test_root, model_name)
df3 = pd.read_csv(csv3)
assert 'hle' in df3['Benchmark'].tolist(), \
f'hle canonical name missing from: {df3["Benchmark"].tolist()}'
assert 'hle_low' not in df3['Benchmark'].tolist(), \
f'hle_low should be aliased away: {df3["Benchmark"].tolist()}'
print(' OK hle_low directory maps to canonical "hle"')
banner('7) Reset recovery — wipe report+predictions, expect archive to restore')
# Wipe the current predictions
shutil.rmtree(preds)
# Reset report.json
data = json.loads(report_json.read_text())
data['perf_metrics']['summary']['n_samples'] = 1
data['perf_metrics']['summary']['latency']['mean'] = 0.0
report_json.write_text(json.dumps(data, indent=2))
csv4, _ = cr.eval_benchmark([benchmark], test_root, model_name)
df4 = pd.read_csv(csv4)
aime24_after = df4[df4['Benchmark'] == 'aime24'].iloc[0]
assert_close(aime24_after['得分'], 0.6333, 'aime24 score (post-reset)')
assert_close(aime24_after['总样本数'], 30, 'aime24 sample count (post-reset)')
assert_close(aime24_after['延迟_mean(s)'], 35.24971, 'aime24 latency mean (post-reset)')
assert_close(aime24_after['TTFT_mean(s)'], 0.26301, 'aime24 TTFT mean (post-reset)')
print(' OK archive fully recovered metrics after predictions wipe')
banner('8) Backup monotonicity — new backup must not overwrite a larger one')
# We have a backup with n_samples=30. Now write a fresh report with
# n_samples=2 and call backup_perf_stats again — the backup should NOT
# be overwritten.
data = json.loads(report_json.read_text())
data['perf_metrics']['summary']['n_samples'] = 2
report_json.write_text(json.dumps(data, indent=2))
pb.backup_perf_stats(test_root, benchmark, model_name, report_json)
payload2 = json.loads(backup_path.read_text())
assert payload2['n_samples'] == 30, \
f'backup was clobbered: n_samples now {payload2["n_samples"]}'
print(' OK backup preserved n_samples=30 even after a reset run')
banner('All checks passed')
print(f'Test artifacts under {test_root} (kept for inspection)')
if __name__ == '__main__':
main()