use std::sync::Barrier;
use std::thread;
use plecto_host::test_support::{TestSigner, bound_sbom};
use plecto_host::{
Header, Host, HttpRequest, LoadOptions, LoadedFilter, LogLine, RequestDecision, RequestTrace,
RunError, SignedArtifact,
};
fn component_bytes() -> Vec<u8> {
std::fs::read(env!("FILTER_HELLO_COMPONENT")).expect("read filter-hello component")
}
fn signed_load(opts: LoadOptions) -> (Host, LoadedFilter) {
let bytes = component_bytes();
let signer = TestSigner::new().unwrap();
let component_signature = signer.sign(&bytes).unwrap();
let sbom = bound_sbom(&bytes);
let sbom_signature = signer.sign(&sbom).unwrap();
let host = Host::new(signer.trust_policy().unwrap()).unwrap();
let artifact = SignedArtifact {
component_bytes: &bytes,
component_signature: &component_signature,
sbom: &sbom,
sbom_signature: &sbom_signature,
};
let filter = host.load("filter-hello", &artifact, opts).unwrap();
(host, filter)
}
fn request(headers: &[(&str, &str)]) -> HttpRequest {
HttpRequest {
method: "GET".to_string(),
path: "/".to_string(),
authority: "example.test".to_string(),
scheme: "https".to_string(),
headers: headers
.iter()
.map(|(n, v)| Header {
name: (*n).to_string(),
value: v.as_bytes().to_vec(),
})
.collect(),
}
}
fn init_calls(logs: &[LogLine]) -> u64 {
logs.iter()
.find_map(|l| {
l.message
.strip_prefix("init-calls=")
.and_then(|n| n.parse().ok())
})
.expect("filter-hello logs init-calls=N every request")
}
const BUSY_OVERLAP_ITERS: u64 = 50_000_000;
const BUSY_HOLD_ITERS: u64 = 1_200_000_000;
#[test]
fn trusted_pool_serves_concurrent_requests() {
let pool_size = 4;
let (_host, filter) = signed_load(
LoadOptions::trusted()
.with_trusted_pool_size(pool_size)
.with_request_deadline_ms(30_000),
);
let threads = 4;
let rounds = 2;
let barrier = Barrier::new(threads);
thread::scope(|s| {
for _ in 0..threads {
s.spawn(|| {
for _ in 0..rounds {
barrier.wait();
let (decision, _logs) = filter
.on_request(
&request(&[("x-plecto-busy", &BUSY_OVERLAP_ITERS.to_string())]),
&RequestTrace::root(),
)
.expect("a concurrent trusted request must succeed");
assert!(
matches!(decision, RequestDecision::Continue),
"a busy-but-benign request should continue"
);
}
});
}
});
let (_decision, logs) = filter
.on_request(&request(&[]), &RequestTrace::root())
.unwrap();
assert!(
init_calls(&logs) >= 2,
"under contention the pool must build >1 instance (init ran {} times)",
init_calls(&logs)
);
}
#[test]
fn trusted_pool_saturation_is_fail_closed() {
let (_host, filter) = signed_load(
LoadOptions::trusted()
.with_trusted_pool_size(1)
.with_request_deadline_ms(30_000)
.with_checkout_timeout_ms(10),
);
thread::scope(|s| {
let holder = s.spawn(|| {
filter
.on_request(
&request(&[("x-plecto-busy", &BUSY_HOLD_ITERS.to_string())]),
&RequestTrace::root(),
)
.expect("the holder's own request still succeeds");
});
thread::sleep(std::time::Duration::from_millis(60));
let contended = filter.on_request(&request(&[]), &RequestTrace::root());
assert!(
matches!(contended, Err(RunError::Unavailable)),
"a saturated trusted pool must fail closed with Unavailable, got {contended:?}"
);
holder.join().unwrap();
});
let (decision, _logs) = filter
.on_request(&request(&[]), &RequestTrace::root())
.expect("the pool recovers once the in-flight request returns");
assert!(matches!(decision, RequestDecision::Continue));
}
#[test]
fn trusted_instance_recycled_after_max_requests() {
let (_host, filter) = signed_load(
LoadOptions::trusted()
.with_trusted_pool_size(1)
.with_max_requests_per_instance(2),
);
let mut seen = Vec::new();
for _ in 0..5 {
let (_decision, logs) = filter
.on_request(&request(&[]), &RequestTrace::root())
.unwrap();
seen.push(init_calls(&logs));
}
assert_eq!(
seen,
vec![1, 1, 2, 2, 3],
"an instance must be recycled (rebuilt, re-init) every 2 requests"
);
}
#[test]
fn trusted_pool_has_no_deadlock_under_churn() {
let (_host, filter) = signed_load(
LoadOptions::trusted()
.with_trusted_pool_size(2)
.with_checkout_timeout_ms(5_000),
);
let threads = 6;
let rounds = 30;
thread::scope(|s| {
for _ in 0..threads {
s.spawn(|| {
for _ in 0..rounds {
let (decision, _logs) = filter
.on_request(&request(&[]), &RequestTrace::root())
.expect("every churned request must complete");
assert!(matches!(decision, RequestDecision::Continue));
}
});
}
});
}