Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion services/ws-modules/face-detection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ thread_local! {

#[wasm_bindgen(start)]
pub fn init() {
tracing_wasm::set_as_global_default();
let _ = tracing_wasm::try_set_as_global_default();
info!("face detection workflow module initialized");
}

Expand Down Expand Up @@ -859,3 +859,6 @@ fn canvas_2d_context(canvas: &HtmlCanvasElement) -> Result<CanvasRenderingContex
fn set_hidden(target: &JsValue, hidden: bool) -> Result<(), JsValue> {
Reflect::set(target, &JsValue::from_str("hidden"), &JsValue::from_bool(hidden)).map(|_| ())
}

#[cfg(test)]
mod test_face_detection;
104 changes: 104 additions & 0 deletions services/ws-modules/face-detection/src/test_face_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::*;

#[test]
fn clamp_bounds_values() {
assert_eq!(clamp(5.0, 0.0, 10.0), 5.0);
assert_eq!(clamp(0.0, 0.0, 10.0), 0.0);
assert_eq!(clamp(10.0, 0.0, 10.0), 10.0);
assert_eq!(clamp(-1.0, 0.0, 10.0), 0.0);
assert_eq!(clamp(11.0, 0.0, 10.0), 10.0);
}

fn detection(score: f64, box_coords: [f64; 4]) -> Detection {
Detection {
label: "face".into(),
class_index: 0,
score,
box_coords,
}
}

#[test]
fn iou_uses_inclusive_pixel_coordinates() {
let left = detection(1.0, [0.0, 0.0, 10.0, 10.0]);
let right = detection(1.0, [5.0, 5.0, 15.0, 15.0]);

let iou = compute_iou(&left, &right);

assert!((iou - (36.0 / 206.0)).abs() < 1e-6);
}

#[test]
fn iou_handles_identical_and_non_overlapping_boxes() {
let left = detection(1.0, [0.0, 0.0, 10.0, 10.0]);
let identical = detection(1.0, [0.0, 0.0, 10.0, 10.0]);
let separate = detection(1.0, [20.0, 20.0, 30.0, 30.0]);
let corner_touching = detection(1.0, [10.0, 10.0, 20.0, 20.0]);

assert!((compute_iou(&left, &identical) - 1.0).abs() < 1e-6);
assert_eq!(compute_iou(&left, &separate), 0.0);
assert!((compute_iou(&left, &corner_touching) - (1.0 / 241.0)).abs() < 1e-6);
}

#[test]
fn nms_keeps_highest_scored_overlapping_box_and_distant_boxes() {
let detections = vec![
detection(0.7, [50.0, 50.0, 60.0, 60.0]),
detection(0.8, [1.0, 1.0, 11.0, 11.0]),
detection(0.9, [0.0, 0.0, 10.0, 10.0]),
];

let filtered = apply_nms(detections, 0.5);

assert_eq!(filtered.len(), 2);
assert_eq!(filtered[0].score, 0.9);
assert_eq!(filtered[1].score, 0.7);
}

#[test]
fn nms_keeps_boxes_when_iou_equals_threshold() {
let filtered = apply_nms(
vec![
detection(0.9, [0.0, 0.0, 10.0, 10.0]),
detection(0.8, [0.0, 0.0, 10.0, 10.0]),
],
1.0,
);

assert_eq!(filtered.len(), 2);
}

#[test]
fn softmax_handles_empty_equal_and_large_values() {
assert!(softmax(&[]).is_empty());

let equal = softmax(&[4.0, 4.0, 4.0, 4.0]);
assert!(equal.iter().all(|value| (*value - 0.25).abs() < 1e-6));

let large = softmax(&[1000.0, 1001.0]);
assert_eq!(large.len(), 2);
assert!(large.iter().all(|value| value.is_finite()));
assert!((large.iter().sum::<f64>() - 1.0).abs() < 1e-6);
assert!(large[1] > large[0]);
}

#[test]
fn retinaface_prior_count_matches_model_input_shape() {
let priors = build_retinaface_priors(FACE_INPUT_HEIGHT_F64, FACE_INPUT_WIDTH_F64);

assert_eq!(priors.len(), 15_960);
assert!((priors[0][0] - (4.0 / FACE_INPUT_WIDTH_F64)).abs() < 1e-6);
assert!((priors[0][1] - (4.0 / FACE_INPUT_HEIGHT_F64)).abs() < 1e-6);
assert!((priors[0][2] - (16.0 / FACE_INPUT_WIDTH_F64)).abs() < 1e-6);
assert!((priors[0][3] - (16.0 / FACE_INPUT_HEIGHT_F64)).abs() < 1e-6);
}

#[test]
fn retinaface_zero_offsets_decode_to_prior_box() {
let decoded = decode_retinaface_box([0.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.25, 0.5]);

assert!((decoded[0] - 0.375).abs() < 1e-6);
assert!((decoded[1] - 0.25).abs() < 1e-6);
assert!((decoded[2] - 0.625).abs() < 1e-6);
assert!((decoded[3] - 0.75).abs() < 1e-6);
}
34 changes: 34 additions & 0 deletions services/ws-modules/face-detection/tests/web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![cfg(target_arch = "wasm32")]
use et_ws_face_detection::{init, is_running, run, stop};
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn init_can_be_called_more_than_once() {
init();
init();
}

#[wasm_bindgen_test]
fn stop_is_idempotent_when_runtime_has_not_started() {
assert!(!is_running());
stop().expect("stop should succeed when face detection is not running");
assert!(!is_running());
}

#[wasm_bindgen_test]
async fn run_failure_leaves_runtime_stopped() {
let result = run().await;

match result {
Ok(()) => {
assert!(is_running());
stop().expect("stop should succeed after a successful run");
assert!(!is_running());
}
Err(_) => {
assert!(!is_running());
}
}
}
5 changes: 4 additions & 1 deletion services/ws-modules/har1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const HAR_CLASS_LABELS: [&str; 6] = ["class_0", "class_1", "class_2", "class_3",

#[wasm_bindgen(start)]
pub fn init() {
tracing_wasm::set_as_global_default();
let _ = tracing_wasm::try_set_as_global_default();
info!("har1 workflow module initialized");
}

Expand Down Expand Up @@ -528,3 +528,6 @@ async fn sleep_ms(duration_ms: i32) -> Result<(), JsValue> {
});
JsFuture::from(promise).await.map(|_| ())
}

#[cfg(test)]
mod test_har1;
72 changes: 72 additions & 0 deletions services/ws-modules/har1/src/test_har1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use super::*;

#[test]
fn softmax_distribution_preserves_order_and_normalizes() {
let logits = vec![2.0, 1.0, 0.1];
let probs = softmax(&logits);

assert_eq!(probs.len(), 3);
let sum: f64 = probs.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
assert!(probs[0] > probs[1]);
assert!(probs[1] > probs[2]);
}

#[test]
fn softmax_handles_empty_equal_and_large_values() {
assert!(softmax(&[]).is_empty());

let equal = softmax(&[7.0, 7.0, 7.0]);
assert!(equal.iter().all(|value| (*value - (1.0 / 3.0)).abs() < 1e-6));

let large = softmax(&[1000.0, 1001.0, 999.0]);
assert_eq!(large.len(), 3);
assert!(large.iter().all(|value| value.is_finite()));
assert!((large.iter().sum::<f64>() - 1.0).abs() < 1e-6);
assert!(large[1] > large[0]);
assert!(large[0] > large[2]);
}

#[test]
fn gravity_and_rotation_conversions_handle_positive_negative_and_zero() {
assert_eq!(to_g(0.0), 0.0);
assert!((to_g(9.80665) - 1.0).abs() < 1e-6);
assert!((to_g(-9.80665) + 1.0).abs() < 1e-6);

assert_eq!(degrees_to_radians(0.0), 0.0);
assert!((degrees_to_radians(180.0) - std::f64::consts::PI).abs() < 1e-6);
assert!((degrees_to_radians(-90.0) + std::f64::consts::FRAC_PI_2).abs() < 1e-6);
}

#[test]
fn flatten_samples_preserves_sample_order_and_feature_order() {
let mut samples = VecDeque::new();
let mut first = [0.0; HAR_FEATURE_COUNT];
let mut second = [0.0; HAR_FEATURE_COUNT];
for index in 0..HAR_FEATURE_COUNT {
first[index] = index as f32;
second[index] = (10 + index) as f32;
}
samples.push_back(first);
samples.push_back(second);

let flattened = flatten_samples(&samples);

assert_eq!(flattened.len(), 2 * HAR_FEATURE_COUNT);
assert_eq!(&flattened[..HAR_FEATURE_COUNT], &first);
assert_eq!(&flattened[HAR_FEATURE_COUNT..], &second);
}

#[test]
fn flatten_samples_handles_empty_buffer() {
let samples = VecDeque::new();

assert!(flatten_samples(&samples).is_empty());
}

#[test]
fn format_number_rejects_non_finite_values() {
assert_eq!(format_number(12.3456, 2), "12.35");
assert_eq!(format_number(f64::NAN, 2), "n/a");
assert_eq!(format_number(f64::INFINITY, 2), "n/a");
}
18 changes: 18 additions & 0 deletions services/ws-modules/har1/tests/web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg(target_arch = "wasm32")]
use et_ws_har1::{init, run};
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn init_can_be_called_more_than_once() {
init();
init();
}

#[wasm_bindgen_test]
async fn run_reports_environment_error_in_headless_browser() {
let result = run().await;

assert!(result.is_err());
}
Loading