chore: import upstream snapshot with attribution
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
Continuous Deployment / Deploy to Production (push) Has been cancelled
Continuous Deployment / Rollback Deployment (push) Has been cancelled
Continuous Deployment / Post-deployment Monitoring (push) Has been cancelled
Continuous Deployment / Notify Deployment Status (push) Has been cancelled
Continuous Deployment / Deploy to Staging (push) Has been cancelled
Security Scanning / Security Report (push) Has been cancelled
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
Continuous Deployment / Deploy to Production (push) Has been cancelled
Continuous Deployment / Rollback Deployment (push) Has been cancelled
Continuous Deployment / Post-deployment Monitoring (push) Has been cancelled
Continuous Deployment / Notify Deployment Status (push) Has been cancelled
Continuous Deployment / Deploy to Staging (push) Has been cancelled
Security Scanning / Security Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
"""ADR-117 P3 — Tests for vital-sign extraction bindings.
|
||||
|
||||
Covers:
|
||||
|
||||
- VitalStatus enum (eq, eq_int, hash, frozen)
|
||||
- VitalEstimate construction + getters + immutability
|
||||
- VitalReading composite + getters
|
||||
- BreathingExtractor + HeartRateExtractor — esp32_default, explicit
|
||||
ctor, extract() return type, validation behaviour
|
||||
|
||||
The Rust pipeline is unit-tested in `v2/crates/wifi-densepose-vitals/`.
|
||||
These tests are deliberately scoped to the *binding* layer — does the
|
||||
Python surface return the right shapes, raise the right errors, and
|
||||
release the GIL safely.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from random import Random
|
||||
|
||||
import pytest
|
||||
|
||||
import wifi_densepose
|
||||
from wifi_densepose import (
|
||||
BreathingExtractor,
|
||||
HeartRateExtractor,
|
||||
VitalEstimate,
|
||||
VitalReading,
|
||||
VitalStatus,
|
||||
)
|
||||
|
||||
|
||||
# ─── VitalStatus enum ────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_vital_status_variants_present() -> None:
|
||||
assert VitalStatus.Valid != VitalStatus.Degraded
|
||||
assert VitalStatus.Unreliable != VitalStatus.Unavailable
|
||||
|
||||
|
||||
def test_vital_status_equality_against_int() -> None:
|
||||
# eq_int → enum can be compared to int (PyO3 0.22 surface)
|
||||
assert VitalStatus.Valid == 0
|
||||
assert VitalStatus.Unavailable == 3
|
||||
|
||||
|
||||
def test_vital_status_is_hashable() -> None:
|
||||
# frozen + hash → can be used as dict key / set member
|
||||
s = {VitalStatus.Valid, VitalStatus.Valid, VitalStatus.Degraded}
|
||||
assert len(s) == 2
|
||||
|
||||
|
||||
def test_vital_status_repr_contains_variant_name() -> None:
|
||||
r = repr(VitalStatus.Valid)
|
||||
assert "VitalStatus" in r and "Valid" in r
|
||||
|
||||
|
||||
# ─── VitalEstimate ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_vital_estimate_construction_and_getters() -> None:
|
||||
est = VitalEstimate(value_bpm=72.4, confidence=0.85, status=VitalStatus.Valid)
|
||||
assert math.isclose(est.value_bpm, 72.4)
|
||||
assert math.isclose(est.confidence, 0.85)
|
||||
assert est.status == VitalStatus.Valid
|
||||
|
||||
|
||||
def test_vital_estimate_is_frozen() -> None:
|
||||
est = VitalEstimate(value_bpm=72.0, confidence=0.9, status=VitalStatus.Valid)
|
||||
with pytest.raises(AttributeError):
|
||||
est.value_bpm = 100.0 # type: ignore[misc]
|
||||
|
||||
|
||||
def test_vital_estimate_repr_is_readable() -> None:
|
||||
est = VitalEstimate(value_bpm=72.0, confidence=0.9, status=VitalStatus.Valid)
|
||||
r = repr(est)
|
||||
assert "VitalEstimate" in r
|
||||
assert "72" in r
|
||||
|
||||
|
||||
# ─── VitalReading ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_vital_reading_construction_and_getters() -> None:
|
||||
br = VitalEstimate(value_bpm=14.0, confidence=0.9, status=VitalStatus.Valid)
|
||||
hr = VitalEstimate(value_bpm=72.0, confidence=0.8, status=VitalStatus.Degraded)
|
||||
reading = VitalReading(
|
||||
respiratory_rate=br,
|
||||
heart_rate=hr,
|
||||
subcarrier_count=56,
|
||||
signal_quality=0.77,
|
||||
timestamp_secs=1700000000.5,
|
||||
)
|
||||
assert reading.respiratory_rate.value_bpm == 14.0
|
||||
assert reading.heart_rate.status == VitalStatus.Degraded
|
||||
assert reading.subcarrier_count == 56
|
||||
assert math.isclose(reading.signal_quality, 0.77)
|
||||
assert math.isclose(reading.timestamp_secs, 1700000000.5)
|
||||
|
||||
|
||||
# ─── BreathingExtractor ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_breathing_esp32_default_constructs() -> None:
|
||||
br = BreathingExtractor.esp32_default()
|
||||
assert br is not None
|
||||
assert "BreathingExtractor" in repr(br)
|
||||
|
||||
|
||||
def test_breathing_explicit_ctor() -> None:
|
||||
br = BreathingExtractor(n_subcarriers=64, sample_rate=200.0, window_secs=20.0)
|
||||
assert br is not None
|
||||
|
||||
|
||||
def test_breathing_extract_returns_none_with_too_few_samples() -> None:
|
||||
"""One frame can't produce a 30-second window — must return None.
|
||||
|
||||
Verifies the binding propagates Rust's `Option<VitalEstimate>` →
|
||||
Python None correctly (vs raising or returning a default).
|
||||
"""
|
||||
br = BreathingExtractor.esp32_default()
|
||||
out = br.extract(residuals=[0.0] * 56, weights=[])
|
||||
assert out is None
|
||||
|
||||
|
||||
def test_breathing_extract_accepts_empty_weights() -> None:
|
||||
"""Empty weights vector means "equal weight per subcarrier" by
|
||||
convention (per breathing.rs)."""
|
||||
br = BreathingExtractor.esp32_default()
|
||||
out = br.extract(residuals=[0.01] * 56, weights=[])
|
||||
# Even with synthetic input it may return None until enough history
|
||||
# accumulates — what matters is that the call doesn't panic.
|
||||
assert out is None or isinstance(out, VitalEstimate)
|
||||
|
||||
|
||||
def test_breathing_extract_with_synthetic_signal() -> None:
|
||||
"""Drive the extractor with a synthetic 0.25 Hz sine (15 BPM) for
|
||||
enough samples to fill the 30-second window. Don't assert the exact
|
||||
BPM — just that the extractor *eventually* produces a result (rather
|
||||
than returning None forever)."""
|
||||
br = BreathingExtractor.esp32_default()
|
||||
sample_rate = 100.0
|
||||
target_freq = 0.25 # 15 BPM
|
||||
# Run 40 seconds of synthetic data — comfortably past the 30s window.
|
||||
n_samples = int(40 * sample_rate)
|
||||
weights = [1.0] * 56
|
||||
|
||||
produced_estimate = False
|
||||
rng = Random(42)
|
||||
for i in range(n_samples):
|
||||
t = i / sample_rate
|
||||
base = math.sin(2.0 * math.pi * target_freq * t)
|
||||
# Per-subcarrier residual: same signal + small per-carrier noise
|
||||
residuals = [base + rng.gauss(0.0, 0.01) for _ in range(56)]
|
||||
est = br.extract(residuals=residuals, weights=weights)
|
||||
if est is not None:
|
||||
produced_estimate = True
|
||||
assert isinstance(est.value_bpm, float)
|
||||
assert 0.0 <= est.confidence <= 1.0
|
||||
assert est.status in (
|
||||
VitalStatus.Valid,
|
||||
VitalStatus.Degraded,
|
||||
VitalStatus.Unreliable,
|
||||
VitalStatus.Unavailable,
|
||||
)
|
||||
break
|
||||
|
||||
assert produced_estimate, "BreathingExtractor never produced an estimate after 40s of synthetic data"
|
||||
|
||||
|
||||
# ─── HeartRateExtractor ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_heart_rate_esp32_default_constructs() -> None:
|
||||
hr = HeartRateExtractor.esp32_default()
|
||||
assert hr is not None
|
||||
assert "HeartRateExtractor" in repr(hr)
|
||||
|
||||
|
||||
def test_heart_rate_explicit_ctor() -> None:
|
||||
hr = HeartRateExtractor(n_subcarriers=64, sample_rate=200.0, window_secs=10.0)
|
||||
assert hr is not None
|
||||
|
||||
|
||||
def test_heart_rate_extract_returns_none_with_too_few_samples() -> None:
|
||||
hr = HeartRateExtractor.esp32_default()
|
||||
out = hr.extract(residuals=[0.0] * 56, weights=[])
|
||||
assert out is None
|
||||
|
||||
|
||||
# ─── Build feature flag ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_p3_vitals_in_build_features() -> None:
|
||||
assert "p3-vitals-bindings" in wifi_densepose.__build_features__
|
||||
Reference in New Issue
Block a user