manifest decode & profile validate optimization
This commit is contained in:
320
tests/bench_manifest_decode_profile.rs
Normal file
320
tests/bench_manifest_decode_profile.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
fn default_samples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/benchmark/selected_der")
|
||||
}
|
||||
|
||||
fn read_samples(dir: &Path) -> Vec<Sample> {
|
||||
let mut out = Vec::new();
|
||||
let rd = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display()));
|
||||
for ent in rd.flatten() {
|
||||
let path = ent.path();
|
||||
if path.extension().and_then(|s| s.to_str()) != Some("mft") {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
out.push(Sample { name, path });
|
||||
}
|
||||
out.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
out
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Sample {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
fn env_u64(name: &str, default: u64) -> u64 {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn env_u64_opt(name: &str) -> Option<u64> {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> bool {
|
||||
matches!(
|
||||
std::env::var(name).as_deref(),
|
||||
Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES")
|
||||
)
|
||||
}
|
||||
|
||||
fn env_string(name: &str) -> Option<String> {
|
||||
std::env::var(name).ok().filter(|s| !s.trim().is_empty())
|
||||
}
|
||||
|
||||
fn escape_md(s: &str) -> String {
|
||||
s.replace('|', "\\|").replace('\n', " ")
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual performance benchmark; prints Markdown table"]
|
||||
fn manifest_decode_profile_benchmark_selected_der() {
|
||||
let dir = env_string("BENCH_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(default_samples_dir);
|
||||
|
||||
let sample_filter = env_string("BENCH_SAMPLE");
|
||||
let fixed_iters = env_u64_opt("BENCH_ITERS");
|
||||
let warmup_iters = env_u64("BENCH_WARMUP_ITERS", 100);
|
||||
let rounds = env_u64("BENCH_ROUNDS", 5);
|
||||
let min_round_ms = env_u64("BENCH_MIN_ROUND_MS", 200);
|
||||
let max_adaptive_iters = env_u64("BENCH_MAX_ITERS", 1_000_000);
|
||||
let verbose = env_bool("BENCH_VERBOSE");
|
||||
let out_md = env_string("BENCH_OUT_MD").map(|p| PathBuf::from(p));
|
||||
let out_json = env_string("BENCH_OUT_JSON").map(|p| PathBuf::from(p));
|
||||
|
||||
if let Some(n) = fixed_iters {
|
||||
assert!(n >= 1, "BENCH_ITERS must be >= 1");
|
||||
}
|
||||
assert!(rounds >= 1, "BENCH_ROUNDS must be >= 1");
|
||||
assert!(min_round_ms >= 1, "BENCH_MIN_ROUND_MS must be >= 1");
|
||||
assert!(max_adaptive_iters >= 1, "BENCH_MAX_ITERS must be >= 1");
|
||||
|
||||
let mut samples = read_samples(&dir);
|
||||
assert!(
|
||||
!samples.is_empty(),
|
||||
"no .mft files found under: {}",
|
||||
dir.display()
|
||||
);
|
||||
|
||||
if let Some(filter) = sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
assert!(!samples.is_empty(), "no sample matched BENCH_SAMPLE={filter}");
|
||||
}
|
||||
|
||||
println!("# Manifest decode + profile validate benchmark (debug build)");
|
||||
println!();
|
||||
println!("- dir: {}", dir.display());
|
||||
if let Some(n) = fixed_iters {
|
||||
println!("- iters: {} (fixed)", n);
|
||||
} else {
|
||||
println!(
|
||||
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
|
||||
warmup_iters, rounds, min_round_ms, max_adaptive_iters
|
||||
);
|
||||
}
|
||||
if let Some(filter) = sample_filter.as_deref() {
|
||||
println!("- sample: {}", filter);
|
||||
}
|
||||
if verbose {
|
||||
println!("- verbose: true");
|
||||
}
|
||||
if let Some(p) = out_md.as_ref() {
|
||||
println!("- out_md: {}", p.display());
|
||||
}
|
||||
if let Some(p) = out_json.as_ref() {
|
||||
println!("- out_json: {}", p.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Samples:");
|
||||
for s in &samples {
|
||||
println!("- {}", s.name);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("| sample | file_count | avg ns/op | ops/s |");
|
||||
println!("|---|---:|---:|---:|");
|
||||
|
||||
let mut rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
|
||||
for s in samples {
|
||||
let bytes =
|
||||
std::fs::read(&s.path).unwrap_or_else(|e| panic!("read {}: {e}", s.path.display()));
|
||||
|
||||
let file_count = ManifestObject::decode_der(bytes.as_slice())
|
||||
.unwrap_or_else(|e| panic!("decode {}: {e}", s.name))
|
||||
.manifest
|
||||
.file_count();
|
||||
|
||||
// Warm-up: exercise the exact decode path but don't time it.
|
||||
for _ in 0..warmup_iters {
|
||||
let input = std::hint::black_box(bytes.as_slice());
|
||||
let decoded = ManifestObject::decode_der(input).expect("decode");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
|
||||
let mut per_round_ns_per_op = Vec::with_capacity(rounds as usize);
|
||||
for round in 0..rounds {
|
||||
let iters = if let Some(n) = fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
bytes.as_slice(),
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
)
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
let input = std::hint::black_box(bytes.as_slice());
|
||||
let decoded = ManifestObject::decode_der(input).expect("decode");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||
let ns_per_op = total_ns / (iters as f64);
|
||||
per_round_ns_per_op.push(ns_per_op);
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"# {} round {}: iters={} total_ms={:.2} ns/op={:.2}",
|
||||
s.name,
|
||||
round + 1,
|
||||
iters,
|
||||
elapsed.as_secs_f64() * 1e3,
|
||||
ns_per_op
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let avg_ns = per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||
|
||||
println!(
|
||||
"| {} | {} | {:.2} | {:.2} |",
|
||||
s.name, file_count, avg_ns, ops_per_sec
|
||||
);
|
||||
|
||||
rows.push(ResultRow {
|
||||
sample: s.name,
|
||||
file_count,
|
||||
avg_ns_per_op: avg_ns,
|
||||
ops_per_sec,
|
||||
});
|
||||
}
|
||||
|
||||
if out_md.is_some() || out_json.is_some() {
|
||||
let timestamp_utc =
|
||||
time::OffsetDateTime::now_utc().format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
let cfg = RunConfig {
|
||||
dir: dir.display().to_string(),
|
||||
sample: sample_filter,
|
||||
fixed_iters,
|
||||
warmup_iters,
|
||||
rounds,
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
timestamp_utc,
|
||||
};
|
||||
|
||||
if let Some(path) = out_md {
|
||||
let md = render_markdown(&cfg, &rows);
|
||||
write_text_file(&path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = out_json {
|
||||
let json = serde_json::to_string_pretty(&BenchmarkOutput { config: cfg, rows })
|
||||
.expect("serialize json");
|
||||
write_text_file(&path, &json);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_iters_adaptive(bytes: &[u8], min_round_ms: u64, max_iters: u64) -> u64 {
|
||||
let min_secs = (min_round_ms as f64) / 1e3;
|
||||
let mut iters: u64 = 1;
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
let input = std::hint::black_box(bytes);
|
||||
let decoded = ManifestObject::decode_der(input).expect("decode");
|
||||
std::hint::black_box(decoded);
|
||||
}
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
if elapsed >= min_secs {
|
||||
return iters;
|
||||
}
|
||||
if iters >= max_iters {
|
||||
return iters;
|
||||
}
|
||||
iters = (iters.saturating_mul(2)).min(max_iters);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_markdown(cfg: &RunConfig, rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("# Manifest decode + profile validate benchmark (debug build)\n\n");
|
||||
out.push_str(&format!("- timestamp_utc: {}\n", cfg.timestamp_utc));
|
||||
out.push_str(&format!("- dir: `{}`\n", cfg.dir));
|
||||
if let Some(s) = cfg.sample.as_deref() {
|
||||
out.push_str(&format!("- sample: `{}`\n", s));
|
||||
}
|
||||
if let Some(n) = cfg.fixed_iters {
|
||||
out.push_str(&format!("- iters: {} (fixed)\n", n));
|
||||
} else {
|
||||
out.push_str(&format!(
|
||||
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})\n",
|
||||
cfg.warmup_iters, cfg.rounds, cfg.min_round_ms, cfg.max_adaptive_iters
|
||||
));
|
||||
}
|
||||
out.push('\n');
|
||||
out.push_str("| sample | file_count | avg ns/op | ops/s |\n");
|
||||
out.push_str("|---|---:|---:|---:|\n");
|
||||
for r in rows {
|
||||
out.push_str(&format!(
|
||||
"| {} | {} | {:.2} | {:.2} |\n",
|
||||
escape_md(&r.sample),
|
||||
r.file_count,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn write_text_file(path: &Path, content: &str) {
|
||||
create_parent_dirs(path);
|
||||
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct RunConfig {
|
||||
dir: String,
|
||||
sample: Option<String>,
|
||||
fixed_iters: Option<u64>,
|
||||
warmup_iters: u64,
|
||||
rounds: u64,
|
||||
min_round_ms: u64,
|
||||
max_adaptive_iters: u64,
|
||||
timestamp_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct ResultRow {
|
||||
sample: String,
|
||||
file_count: usize,
|
||||
avg_ns_per_op: f64,
|
||||
ops_per_sec: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
struct BenchmarkOutput {
|
||||
config: RunConfig,
|
||||
rows: Vec<ResultRow>,
|
||||
}
|
||||
87
tests/benchmark/flamegraph_manifest_decode_profile.sh
Executable file
87
tests/benchmark/flamegraph_manifest_decode_profile.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Generates a flamegraph SVG for Manifest decode+profile validate.
|
||||
#
|
||||
# Prereqs:
|
||||
# - `perf` installed and usable by current user
|
||||
# - `cargo flamegraph` installed (`cargo install flamegraph`)
|
||||
#
|
||||
# Usage examples:
|
||||
# BENCH_SAMPLE=large-02 ./tests/benchmark/flamegraph_manifest_decode_profile.sh
|
||||
# BENCH_SAMPLE=large-02 BENCH_ITERS=5000 FLAMEGRAPH_PROFILE=dev ./tests/benchmark/flamegraph_manifest_decode_profile.sh
|
||||
# BENCH_SAMPLE=large-02 BENCH_ITERS=2000 FLAMEGRAPH_PROFILE=release ./tests/benchmark/flamegraph_manifest_decode_profile.sh
|
||||
|
||||
PROFILE="${FLAMEGRAPH_PROFILE:-dev}" # dev|release
|
||||
SAMPLE="${BENCH_SAMPLE:-large-02}"
|
||||
|
||||
ITERS="${BENCH_ITERS:-2000}"
|
||||
FREQ="${FLAMEGRAPH_FREQ:-99}"
|
||||
WARMUP="${BENCH_WARMUP_ITERS:-1}"
|
||||
ROUNDS="${BENCH_ROUNDS:-1}"
|
||||
MIN_ROUND_MS="${BENCH_MIN_ROUND_MS:-1}"
|
||||
MAX_ITERS="${BENCH_MAX_ITERS:-100000}"
|
||||
|
||||
if ! command -v perf >/dev/null 2>&1; then
|
||||
echo "ERROR: perf not found. Install linux-tools/perf first (may require sudo)." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v cargo-flamegraph >/dev/null 2>&1 && ! command -v flamegraph >/dev/null 2>&1; then
|
||||
# cargo installs as `cargo flamegraph`, but the binary is `cargo-flamegraph`.
|
||||
if ! command -v cargo-flamegraph >/dev/null 2>&1; then
|
||||
echo "ERROR: cargo-flamegraph not found. Install with: cargo install flamegraph" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
OUT="target/bench/flamegraph_mft_decode_profile_${SAMPLE}_${PROFILE}.svg"
|
||||
|
||||
echo "profile=${PROFILE} sample=${SAMPLE} iters=${ITERS} freq=${FREQ} out=${OUT}"
|
||||
|
||||
FLAGS=()
|
||||
if [[ "${PROFILE}" == "release" ]]; then
|
||||
FLAGS+=(--release)
|
||||
else
|
||||
FLAGS+=(--dev)
|
||||
fi
|
||||
|
||||
# On WSL2, /usr/bin/perf is often a wrapper that exits 2 because there is no
|
||||
# kernel-matched perf binary. In that case, prefer a real perf binary under
|
||||
# /usr/lib/linux-tools/*/perf by putting a shim earlier in PATH.
|
||||
PERF_WRAPPER_OK=1
|
||||
PERF_VERSION_OUT="$(perf --version 2>&1 || true)"
|
||||
if echo "${PERF_VERSION_OUT}" | grep -q "WARNING: perf not found for kernel"; then
|
||||
PERF_WRAPPER_OK=0
|
||||
fi
|
||||
|
||||
if [[ "${PERF_WRAPPER_OK}" == "0" ]]; then
|
||||
PERF_REAL="$(ls -1 /usr/lib/linux-tools/*/perf 2>/dev/null | head -n 1 || true)"
|
||||
if [[ -z "${PERF_REAL}" ]]; then
|
||||
echo "ERROR: perf wrapper found, but no real perf binary under /usr/lib/linux-tools/*/perf" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SHIM_DIR="target/bench/tools"
|
||||
mkdir -p "${SHIM_DIR}"
|
||||
cat > "${SHIM_DIR}/perf" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
exec "${PERF_REAL}" "\$@"
|
||||
EOF
|
||||
chmod +x "${SHIM_DIR}/perf"
|
||||
export PATH="$(pwd)/${SHIM_DIR}:${PATH}"
|
||||
echo "Using perf shim -> ${PERF_REAL}"
|
||||
fi
|
||||
|
||||
BENCH_SAMPLE="${SAMPLE}" \
|
||||
BENCH_ITERS="${ITERS}" \
|
||||
BENCH_WARMUP_ITERS="${WARMUP}" \
|
||||
BENCH_ROUNDS="${ROUNDS}" \
|
||||
BENCH_MIN_ROUND_MS="${MIN_ROUND_MS}" \
|
||||
BENCH_MAX_ITERS="${MAX_ITERS}" \
|
||||
cargo flamegraph "${FLAGS[@]}" \
|
||||
-F "${FREQ}" \
|
||||
--output "${OUT}" \
|
||||
--test bench_manifest_decode_profile -- --ignored --nocapture
|
||||
|
||||
echo "Wrote ${OUT}"
|
||||
BIN
tests/benchmark/selected_der/large-01.mft
Normal file
BIN
tests/benchmark/selected_der/large-01.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/large-02.mft
Normal file
BIN
tests/benchmark/selected_der/large-02.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/medium-01.mft
Normal file
BIN
tests/benchmark/selected_der/medium-01.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/medium-02.mft
Normal file
BIN
tests/benchmark/selected_der/medium-02.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/small-01.mft
Normal file
BIN
tests/benchmark/selected_der/small-01.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/small-02.mft
Normal file
BIN
tests/benchmark/selected_der/small-02.mft
Normal file
Binary file not shown.
9
tests/benchmark/selected_der/sources.tsv
Normal file
9
tests/benchmark/selected_der/sources.tsv
Normal file
@@ -0,0 +1,9 @@
|
||||
name input_path der_path input_size der_size source_format
|
||||
large-01 benchmark/fixtures/selected/large-01.mft benchmark/fixtures/selected_der/large-01.mft 262869 2250 stored_point_container
|
||||
large-02 benchmark/fixtures/selected/large-02.mft benchmark/fixtures/selected_der/large-02.mft 264331 10476 stored_point_container
|
||||
medium-01 benchmark/fixtures/selected/medium-01.mft benchmark/fixtures/selected_der/medium-01.mft 16384 2681 stored_point_container
|
||||
medium-02 benchmark/fixtures/selected/medium-02.mft benchmark/fixtures/selected_der/medium-02.mft 16384 2681 stored_point_container
|
||||
small-01 benchmark/fixtures/selected/small-01.mft benchmark/fixtures/selected_der/small-01.mft 3160 1875 stored_point_container
|
||||
small-02 benchmark/fixtures/selected/small-02.mft benchmark/fixtures/selected_der/small-02.mft 3160 1875 stored_point_container
|
||||
xlarge-01 benchmark/fixtures/selected/xlarge-01.mft benchmark/fixtures/selected_der/xlarge-01.mft 4522145 144968 stored_point_container
|
||||
xlarge-02 benchmark/fixtures/selected/xlarge-02.mft benchmark/fixtures/selected_der/xlarge-02.mft 4615094 150261 stored_point_container
|
||||
|
BIN
tests/benchmark/selected_der/xlarge-01.mft
Normal file
BIN
tests/benchmark/selected_der/xlarge-01.mft
Normal file
Binary file not shown.
BIN
tests/benchmark/selected_der/xlarge-02.mft
Normal file
BIN
tests/benchmark/selected_der/xlarge-02.mft
Normal file
Binary file not shown.
@@ -59,8 +59,15 @@ fn store_raw_publication_point_files(
|
||||
store
|
||||
.put_raw(manifest_rsync_uri, manifest_bytes)
|
||||
.expect("store manifest raw");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
@@ -80,8 +80,15 @@ fn cache_pack_publication_point_mismatch_is_rejected() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
@@ -18,13 +18,16 @@ fn decode_manifest_fixture_smoke() {
|
||||
rpki::data_model::oid::OID_SHA256
|
||||
);
|
||||
assert!(mft.manifest.next_update > mft.manifest.this_update);
|
||||
assert!(!mft.manifest.files.is_empty());
|
||||
assert!(mft.manifest.file_count() > 0);
|
||||
// The manifest file MUST NOT be listed in its own fileList.
|
||||
let entries = mft
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
assert!(
|
||||
mft.manifest
|
||||
.files
|
||||
entries
|
||||
.iter()
|
||||
.all(|f| !f.file_name.to_ascii_lowercase().ends_with(".mft"))
|
||||
.all(|f| !f.file_name.as_str().to_ascii_lowercase().ends_with(".mft"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,15 @@ fn manifest_success_writes_fetch_cache_pp_pack() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
@@ -111,8 +118,15 @@ fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
@@ -140,11 +154,11 @@ fn manifest_hash_mismatch_falls_back_to_fetch_cache_pp_when_enabled() {
|
||||
.expect("fetch_cache_pp pack exists");
|
||||
let cached_pack = FetchCachePpPack::decode(&cached_bytes).expect("decode cached");
|
||||
|
||||
let victim = manifest
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.files
|
||||
.first()
|
||||
.expect("non-empty file list");
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
let victim = entries.first().expect("non-empty file list");
|
||||
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
|
||||
let mut tampered = store
|
||||
.get_raw(&victim_uri)
|
||||
@@ -186,8 +200,15 @@ fn manifest_failed_fetch_stop_all_output() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
@@ -208,11 +229,11 @@ fn manifest_failed_fetch_stop_all_output() {
|
||||
)
|
||||
.expect("first run stores fetch_cache_pp pack");
|
||||
|
||||
let victim = manifest
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.files
|
||||
.first()
|
||||
.expect("non-empty file list");
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
let victim = entries.first().expect("non-empty file list");
|
||||
let victim_uri = format!("{publication_point_rsync_uri}{}", victim.file_name);
|
||||
let mut tampered = store
|
||||
.get_raw(&victim_uri)
|
||||
@@ -255,8 +276,15 @@ fn manifest_fallback_pack_is_revalidated_and_rejected_if_stale() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
@@ -314,8 +342,15 @@ fn manifest_replay_is_treated_as_failed_fetch_and_uses_fetch_cache_pp() {
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
@@ -41,8 +41,12 @@ fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
|
||||
|
||||
// Store all referenced files under the (different) publication point so that §6.4/§6.5
|
||||
// would otherwise succeed if §6.1 was not enforced.
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = fixture_dir.join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = fixture_dir.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
@@ -319,13 +319,16 @@ struct ManifestEContentPretty {
|
||||
|
||||
impl From<&ManifestEContent> for ManifestEContentPretty {
|
||||
fn from(v: &ManifestEContent) -> Self {
|
||||
let entries = v
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
Self {
|
||||
version: v.version,
|
||||
manifest_number: v.manifest_number.to_hex_upper(),
|
||||
this_update: v.this_update,
|
||||
next_update: v.next_update,
|
||||
file_hash_alg: v.file_hash_alg.clone(),
|
||||
files: v.files.iter().map(FileAndHashPretty::from).collect(),
|
||||
files: entries.iter().map(FileAndHashPretty::from).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,8 +342,8 @@ struct FileAndHashPretty {
|
||||
impl From<&FileAndHash> for FileAndHashPretty {
|
||||
fn from(v: &FileAndHash) -> Self {
|
||||
Self {
|
||||
file_name: v.file_name.clone(),
|
||||
hash_hex: hex::encode(&v.hash_bytes),
|
||||
file_name: v.file_name.as_str().to_string(),
|
||||
hash_hex: hex::encode(v.hash_bytes.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,15 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
@@ -51,8 +51,15 @@ fn build_cernet_pack_and_validation_time() -> (
|
||||
store
|
||||
.put_raw(&manifest_rsync_uri, &manifest_bytes)
|
||||
.expect("store manifest");
|
||||
for entry in &manifest.manifest.files {
|
||||
let file_path = manifest_path.parent().unwrap().join(&entry.file_name);
|
||||
let entries = manifest
|
||||
.manifest
|
||||
.parse_files()
|
||||
.expect("parse validated manifest fileList");
|
||||
for entry in &entries {
|
||||
let file_path = manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(entry.file_name.as_str());
|
||||
let bytes = std::fs::read(&file_path)
|
||||
.unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
|
||||
let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
|
||||
|
||||
33
tests/test_rpki_bin_coverage.rs
Normal file
33
tests/test_rpki_bin_coverage.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn rpki_bin_help_exits_success_and_prints_usage() {
|
||||
// This also increases coverage for `src/bin/rpki.rs` because it executes the binary.
|
||||
let exe = env!("CARGO_BIN_EXE_rpki");
|
||||
let out = Command::new(exe)
|
||||
.arg("--help")
|
||||
.output()
|
||||
.expect("run rpki --help");
|
||||
|
||||
assert!(out.status.success(), "status={}", out.status);
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
// `cli::usage()` contains "Usage:".
|
||||
assert!(
|
||||
stdout.contains("Usage:") || stdout.contains("USAGE:"),
|
||||
"stdout={stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpki_bin_without_args_exits_2_and_prints_error() {
|
||||
let exe = env!("CARGO_BIN_EXE_rpki");
|
||||
let out = Command::new(exe).output().expect("run rpki");
|
||||
|
||||
assert_eq!(out.status.code(), Some(2), "status={}", out.status);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
!stderr.trim().is_empty(),
|
||||
"expected non-empty stderr, got empty"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user