287 lines
10 KiB
Rust
287 lines
10 KiB
Rust
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}");
|
|
}
|