use std::process::Command; #[test] fn report_to_routinator_csv_exports_sorted_rows() { let dir = tempfile::tempdir().expect("tempdir"); let report = dir.path().join("report.json"); let out_csv = dir.path().join("vrps.csv"); std::fs::write( &report, r#"{ "format_version": 2, "meta": {"validation_time_rfc3339_utc": "2026-03-13T02:30:00Z"}, "policy": {}, "tree": {"instances_processed": 0, "instances_failed": 0, "warnings": []}, "publication_points": [], "vrps": [ {"asn": 64497, "prefix": "203.0.113.0/24", "max_length": 24}, {"asn": 64496, "prefix": "192.0.2.0/24", "max_length": 24} ], "aspas": [], "downloads": [], "download_stats": {"events_total": 0, "by_kind": {}} }"#, ) .expect("write report"); let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("scripts/payload_replay/report_to_routinator_csv.py"); let out = Command::new("python3") .arg(&script) .args([ "--report", report.to_string_lossy().as_ref(), "--out", out_csv.to_string_lossy().as_ref(), "--trust-anchor", "apnic", ]) .output() .expect("run export script"); assert!( out.status.success(), "script failed: status={}\nstdout={}\nstderr={}", out.status, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); let csv = std::fs::read_to_string(&out_csv).expect("read csv"); let lines: Vec<_> = csv.lines().collect(); assert_eq!(lines[0], "ASN,IP Prefix,Max Length,Trust Anchor"); assert_eq!(lines[1], "AS64496,192.0.2.0/24,24,apnic"); assert_eq!(lines[2], "AS64497,203.0.113.0/24,24,apnic"); } #[test] fn compare_with_routinator_record_reports_diff_counts() { let dir = tempfile::tempdir().expect("tempdir"); let ours = dir.path().join("ours.csv"); let record = dir.path().join("record.csv"); let summary = dir.path().join("summary.md"); let only_ours = dir.path().join("only_ours.csv"); let only_record = dir.path().join("only_record.csv"); std::fs::write( &ours, "ASN,IP Prefix,Max Length,Trust Anchor\nAS64496,192.0.2.0/24,24,apnic\nAS64497,198.51.100.0/24,24,apnic\n", ) .expect("write ours csv"); std::fs::write( &record, "ASN,IP Prefix,Max Length,Trust Anchor\nAS64496,192.0.2.0/24,24,apnic\nAS64498,203.0.113.0/24,24,apnic\n", ) .expect("write record csv"); let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("scripts/payload_replay/compare_with_routinator_record.sh"); let out = Command::new(&script) .args([ ours.to_string_lossy().as_ref(), record.to_string_lossy().as_ref(), summary.to_string_lossy().as_ref(), only_ours.to_string_lossy().as_ref(), only_record.to_string_lossy().as_ref(), ]) .output() .expect("run compare script"); assert!( out.status.success(), "script failed: status={}\nstdout={}\nstderr={}", out.status, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); let summary_text = std::fs::read_to_string(&summary).expect("read summary"); assert!( summary_text.contains("| ours_total | 2 |"), "{summary_text}" ); assert!( summary_text.contains("| record_total | 2 |"), "{summary_text}" ); assert!( summary_text.contains("| intersection | 1 |"), "{summary_text}" ); assert!( summary_text.contains("| only_in_ours | 1 |"), "{summary_text}" ); assert!( summary_text.contains("| only_in_record | 1 |"), "{summary_text}" ); let only_ours_text = std::fs::read_to_string(&only_ours).expect("read only ours csv"); assert!(only_ours_text.contains("AS64497,198.51.100.0/24,24,apnic")); let only_record_text = std::fs::read_to_string(&only_record).expect("read only record csv"); assert!(only_record_text.contains("AS64498,203.0.113.0/24,24,apnic")); } #[test] fn write_multi_rir_case_report_combines_compare_and_timing() { let dir = tempfile::tempdir().expect("tempdir"); let snapshot_meta = dir.path().join("snapshot_meta.json"); let delta_meta = dir.path().join("delta_meta.json"); let snapshot_compare = dir.path().join("snapshot_compare.md"); let delta_compare = dir.path().join("delta_compare.md"); let out_md = dir.path().join("case_report.md"); let out_json = dir.path().join("case_report.json"); std::fs::write( &snapshot_meta, r#"{ "durations_secs": {"rpki_run": 12}, "counts": {"vrps": 10, "aspas": 1} }"#, ) .expect("write snapshot meta"); std::fs::write( &delta_meta, r#"{ "durations_secs": {"rpki_run": 8}, "counts": {"vrps": 11, "aspas": 1} }"#, ) .expect("write delta meta"); std::fs::write( &snapshot_compare, "# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 10 |\n| record_total | 10 |\n| intersection | 10 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n", ) .expect("write snapshot compare"); std::fs::write( &delta_compare, "# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 11 |\n| record_total | 11 |\n| intersection | 11 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n", ) .expect("write delta compare"); let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("scripts/payload_replay/write_multi_rir_case_report.py"); let out = Command::new("python3") .arg(&script) .args([ "--rir", "afrinic", "--snapshot-meta", snapshot_meta.to_string_lossy().as_ref(), "--snapshot-compare", snapshot_compare.to_string_lossy().as_ref(), "--delta-meta", delta_meta.to_string_lossy().as_ref(), "--delta-compare", delta_compare.to_string_lossy().as_ref(), "--routinator-base-seconds", "6.0", "--routinator-delta-seconds", "4.0", "--out-md", out_md.to_string_lossy().as_ref(), "--out-json", out_json.to_string_lossy().as_ref(), ]) .output() .expect("run case report script"); assert!( out.status.success(), "script failed: status={}\nstdout={}\nstderr={}", out.status, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); let report: serde_json::Value = serde_json::from_slice(&std::fs::read(&out_json).expect("read report json")) .expect("parse report json"); assert_eq!(report["rir"].as_str(), Some("afrinic")); assert_eq!(report["snapshot"]["match"].as_bool(), Some(true)); assert_eq!(report["delta"]["match"].as_bool(), Some(true)); assert_eq!(report["snapshot"]["ratio"].as_f64(), Some(2.0)); assert_eq!(report["delta"]["ratio"].as_f64(), Some(2.0)); let md = std::fs::read_to_string(&out_md).expect("read markdown"); assert!(md.contains("AFRINIC Replay Report"), "{md}"); assert!(md.contains("| snapshot | true | 12.000 | 6.000 | 2.000 | 0 | 0 |"), "{md}"); assert!(md.contains("| delta | true | 8.000 | 4.000 | 2.000 | 0 | 0 |"), "{md}"); } #[test] fn write_multi_rir_summary_aggregates_case_reports() { let dir = tempfile::tempdir().expect("tempdir"); let case_root = dir.path().join("cases"); for (rir, snapshot_ratio, delta_ratio) in [ ("afrinic", 2.0_f64, 3.0_f64), ("apnic", 1.5_f64, 2.5_f64), ("arin", 1.1_f64, 1.6_f64), ("lacnic", 3.8_f64, 4.8_f64), ("ripe", 2.7_f64, 2.6_f64), ] { let rir_dir = case_root.join(rir); std::fs::create_dir_all(&rir_dir).expect("create rir dir"); let report = serde_json::json!({ "rir": rir, "snapshot": { "match": true, "ours_seconds": 10.0, "routinator_seconds": 5.0, "ratio": snapshot_ratio, "compare": {"only_in_ours": 0, "only_in_record": 0} }, "delta": { "match": true, "ours_seconds": 12.0, "routinator_seconds": 6.0, "ratio": delta_ratio, "compare": {"only_in_ours": 0, "only_in_record": 0} } }); std::fs::write( rir_dir.join(format!("{rir}_case_report.json")), serde_json::to_vec_pretty(&report).expect("serialize report"), ) .expect("write report"); } let out_md = dir.path().join("summary.md"); let out_json = dir.path().join("summary.json"); let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("scripts/payload_replay/write_multi_rir_summary.py"); let out = Command::new("python3") .arg(&script) .args([ "--case-root", case_root.to_string_lossy().as_ref(), "--out-md", out_md.to_string_lossy().as_ref(), "--out-json", out_json.to_string_lossy().as_ref(), ]) .output() .expect("run summary script"); assert!( out.status.success(), "script failed: status={}\nstdout={}\nstderr={}", out.status, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); let json: serde_json::Value = serde_json::from_slice(&std::fs::read(&out_json).expect("read summary json")) .expect("parse summary json"); assert_eq!(json["cases"].as_array().map(|v| v.len()), Some(5)); assert_eq!(json["summary"]["snapshot_all_match"].as_bool(), Some(true)); assert_eq!(json["summary"]["delta_all_match"].as_bool(), Some(true)); assert!(json["summary"]["all_ratio_geomean"].as_f64().unwrap_or(0.0) > 0.0); let md = std::fs::read_to_string(&out_md).expect("read summary md"); assert!(md.contains("Multi-RIR Replay Summary"), "{md}"); assert!(md.contains("| afrinic | true | 10.000 | 5.000 | 2.000 | true | 12.000 | 6.000 | 3.000 |"), "{md}"); } #[test] fn apnic_snapshot_profile_script_dry_run_builds_command() { let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("scripts/payload_replay/run_apnic_snapshot_replay_profile.sh"); let out = Command::new("bash") .env("DRY_RUN", "1") .arg(&script) .output() .expect("run profile script dry-run"); assert!( out.status.success(), "script failed: status={}\nstdout={}\nstderr={}", out.status, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); let stdout = String::from_utf8_lossy(&out.stdout); assert!(stdout.contains("--payload-replay-archive"), "{stdout}"); assert!(stdout.contains("--payload-replay-locks"), "{stdout}"); assert!(stdout.contains("--analyze"), "{stdout}"); assert!(stdout.contains("--profile-cpu"), "{stdout}"); }