holperf/tests/head.rs
//! HOL-HEAD-1: every backend answers `HEAD` with the `GET` answer's status and
//! headers, a `Content-Length` naming the `GET` body's length, and no body.
//!
//! RED-when-broken: the znippy backends ignored the method (`let _ = (method, body)`),
//! so a `HEAD` read and returned the whole body (a 64 MiB artifact for a size
//! question), and the raw backend answered `HEAD` without a length.
use traits::RepositoryBackendTrait;
fn content_length(h: &[(String, String)]) -> Option<usize> {
h.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("content-length"))
.and_then(|(_, v)| v.trim().parse().ok())
}
fn check(format: &str, b: &dyn RepositoryBackendTrait, urls: &[String], bad: &mut Vec<String>) {
for u in urls.iter().take(2) {
let (gs, _, gb) = b
.handle_http2_request("GET", u, &[])
.unwrap_or_else(|e| panic!("{format} GET {u}: {e:#}"));
let (hs, hh, hb) = b
.handle_http2_request("HEAD", u, &[])
.unwrap_or_else(|e| panic!("{format} HEAD {u}: {e:#}"));
if hs != gs {
bad.push(format!("{format} {u}: HEAD {hs}, GET {gs}"));
}
if !hb.is_empty() {
bad.push(format!(
"{format} {u}: HEAD returned a {} byte body",
hb.len()
));
}
if gs == 200 && content_length(&hh) != Some(gb.len()) {
bad.push(format!(
"{format} {u}: HEAD Content-Length {:?}, the GET body is {}",
content_length(&hh),
gb.len()
));
}
}
}
#[test]
fn every_backend_answers_head_with_headers_and_no_body() {
let _ = rustls::crypto::ring::default_provider().install_default();
let dir = tempfile::tempdir().unwrap();
let mut bad = Vec::new();
let mut seen = Vec::new();
for sc in holperf::seed::all(dir.path(), 4, 4096, 64 * 1024, None).unwrap() {
let b = (sc.open)();
for urls in [&sc.index, &sc.meta2, &sc.small, &sc.large] {
check(sc.format, b.as_ref(), urls, &mut bad);
}
seen.push(sc.format);
}
let cs = holperf::chain::build(dir.path(), 2, 4096, 64 * 1024, None)
.unwrap()
.expect("the chain scenarios");
for sc in &cs.scenarios {
if !sc.format.starts_with("chain-") || matches!(sc.format, "chain-pip" | "chain-npm") {
continue; // the production and npm doors are the bench's own adapters, not backends
}
let b = (sc.open)();
for urls in [&sc.index, &sc.meta2, &sc.small, &sc.large] {
check(sc.format, b.as_ref(), urls, &mut bad);
}
seen.push(sc.format);
}
assert!(seen.len() >= 18, "every seeded format was asked: {seen:?}");
assert!(
bad.is_empty(),
"{} HEAD answers were wrong:\n{}",
bad.len(),
bad.join("\n")
);
}