wifi-densepose/v2/crates/wifi-densepose-bfld/tests/pipeline_handle_worker.rs

203 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Acceptance tests for `BfldPipelineHandle`. ADR-118 §2.1 worker surface.
#![cfg(feature = "std")]
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use wifi_densepose_bfld::{
BfldConfig, BfldPipeline, BfldPipelineHandle, CapturePublisher, IdentityEmbedding,
PipelineInput, PrivacyClass, SensingInputs, EMBEDDING_DIM,
};
fn inputs(ts_ns: u64) -> SensingInputs {
SensingInputs {
timestamp_ns: ts_ns,
presence: true,
motion: 0.5,
person_count: 1,
sensing_confidence: 0.9,
sep: 0.2,
stab: 0.2,
consist: 0.2,
risk_conf: 0.2,
rf_signature_hash: None,
}
}
fn embedding() -> IdentityEmbedding {
IdentityEmbedding::from_raw([0.05; EMBEDDING_DIM])
}
fn input(ts_ns: u64) -> PipelineInput {
PipelineInput {
inputs: inputs(ts_ns),
embedding: Some(embedding()),
}
}
fn drain(published: &Arc<Mutex<CapturePublisher>>) -> Vec<String> {
published
.lock()
.unwrap()
.published
.iter()
.map(|m| m.topic.clone())
.collect()
}
#[test]
fn handle_publishes_single_input() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
handle.send(input(0)).expect("send must succeed");
// Give the worker a moment to drain the channel.
thread::sleep(Duration::from_millis(50));
handle.shutdown();
let topics = drain(&pub_arc);
assert_eq!(topics.len(), 5, "Anonymous + no zone → 5 topics");
}
#[test]
fn handle_publishes_multiple_inputs_in_order() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
for i in 0..3 {
handle.send(input(i * 1_000_000)).unwrap();
}
thread::sleep(Duration::from_millis(80));
handle.shutdown();
let topics = drain(&pub_arc);
assert_eq!(topics.len(), 15, "3 inputs × 5 topics each = 15");
}
#[test]
fn handle_send_after_shutdown_errors() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc);
// Save the sender by cloning before shutdown — but BfldPipelineHandle
// owns the sender, so the test demonstrates this via post-shutdown send:
handle.shutdown();
// shutdown consumed handle; we can't call send afterward at the type
// level. The compile-time guarantee IS the test.
}
#[test]
fn handle_drop_without_explicit_shutdown_joins_worker_cleanly() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
{
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
handle.send(input(0)).unwrap();
thread::sleep(Duration::from_millis(50));
// No explicit shutdown — Drop must handle worker join.
}
// If we reached here without hanging or panicking, the Drop path worked.
let topics = drain(&pub_arc);
assert_eq!(topics.len(), 5);
}
#[test]
fn handle_honors_privacy_mode_toggle_via_pipeline_state() {
let mut pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
pipeline.enable_privacy_mode();
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
handle.send(input(0)).unwrap();
thread::sleep(Duration::from_millis(50));
handle.shutdown();
let topics = drain(&pub_arc);
// Restricted + no zone: presence/motion/count/confidence = 4 topics.
assert_eq!(topics.len(), 4, "Restricted strips identity_risk topic");
assert!(!topics.iter().any(|t| t.contains("identity_risk")));
}
#[test]
fn handle_drops_event_when_gate_rejects() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
// Two high-risk inputs back-to-back force the gate into Reject after debounce.
use wifi_densepose_bfld::coherence_gate::DEBOUNCE_NS;
let mut high_risk = inputs(0);
high_risk.sep = 1.0;
high_risk.stab = 1.0;
high_risk.consist = 1.0;
high_risk.risk_conf = 0.8;
handle
.send(PipelineInput {
inputs: high_risk.clone(),
embedding: Some(embedding()),
})
.unwrap();
high_risk.timestamp_ns = DEBOUNCE_NS;
handle
.send(PipelineInput {
inputs: high_risk,
embedding: Some(embedding()),
})
.unwrap();
thread::sleep(Duration::from_millis(80));
handle.shutdown();
let topics = drain(&pub_arc);
// First input emits (Accept state) → 5 topics. Second input gate-promoted
// to Reject → 0 topics. Total = 5.
assert_eq!(topics.len(), 5, "Reject must drop the second event entirely");
}
#[test]
fn handle_with_zone_threads_through_to_published_topics() {
let pipeline = BfldPipeline::new(BfldConfig::new("seed-01").with_zone("kitchen"));
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
handle.send(input(0)).unwrap();
thread::sleep(Duration::from_millis(50));
handle.shutdown();
let topics = drain(&pub_arc);
assert!(
topics.iter().any(|t| t.contains("zone_activity")),
"zone_activity topic must be present when zone configured",
);
let zone_msg = pub_arc
.lock()
.unwrap()
.published
.iter()
.find(|m| m.topic.contains("zone_activity"))
.map(|m| m.payload.clone());
assert_eq!(zone_msg.as_deref(), Some("\"kitchen\""));
}
#[test]
fn class_3_pipeline_baseline_produces_four_topics_per_input() {
// Baseline class = Restricted (no privacy_mode toggle needed).
let pipeline = BfldPipeline::new(
BfldConfig::new("seed-01").with_privacy_class(PrivacyClass::Restricted),
);
let pub_arc = Arc::new(Mutex::new(CapturePublisher::default()));
let handle = BfldPipelineHandle::spawn(pipeline, pub_arc.clone());
handle.send(input(0)).unwrap();
thread::sleep(Duration::from_millis(50));
handle.shutdown();
assert_eq!(drain(&pub_arc).len(), 4);
}