9740bc64c9
Continuous Deployment / Deploy to Production (push) Blocked by required conditions
Continuous Deployment / Rollback Deployment (push) Blocked by required conditions
Continuous Deployment / Post-deployment Monitoring (push) Blocked by required conditions
Continuous Deployment / Notify Deployment Status (push) Blocked by required conditions
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
Continuous Deployment / Deploy to Staging (push) Waiting to run
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 / Security Report (push) Waiting to run
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
264 lines
8.6 KiB
Python
264 lines
8.6 KiB
Python
"""ADR-117 P3.5 — Tests for BFLD (Beamforming Feedback Loop Data) bindings.
|
|
|
|
These tests cover the *stub-Rust-backed* forward-compatible Python
|
|
surface defined in ADR-117 §5.7a. The real Rust ingestion crate
|
|
(`wifi-densepose-bfld`) lands post-v2.0; this test suite locks in the
|
|
Python API so a future swap-in is non-breaking.
|
|
|
|
Coverage:
|
|
|
|
- BfldKind enum — HE20/40/80/160 + HT20/40 variants
|
|
- BfldKind metadata getters — n_subcarriers, bandwidth_mhz, is_he
|
|
- BfldFrame.from_compressed_feedback — happy path + dim mismatch
|
|
- BfldFrame numpy round-trip — feedback_matrix returns ndarray
|
|
- BfldReport — frame aggregation, kind-mismatch error, coherence score
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import wifi_densepose
|
|
from wifi_densepose import BfldFrame, BfldKind, BfldReport
|
|
|
|
|
|
# ─── BfldKind enum ───────────────────────────────────────────────────
|
|
|
|
|
|
def test_bfld_kind_variants_exist() -> None:
|
|
assert BfldKind.CompressedHE20 != BfldKind.CompressedHE40
|
|
assert BfldKind.CompressedHE80 != BfldKind.CompressedHE160
|
|
assert BfldKind.UncompressedHT20 != BfldKind.UncompressedHT40
|
|
|
|
|
|
def test_bfld_kind_is_hashable() -> None:
|
|
s = {BfldKind.CompressedHE80, BfldKind.CompressedHE80}
|
|
assert len(s) == 1
|
|
|
|
|
|
def test_bfld_kind_n_subcarriers_he() -> None:
|
|
assert BfldKind.CompressedHE20.n_subcarriers == 242
|
|
assert BfldKind.CompressedHE40.n_subcarriers == 484
|
|
assert BfldKind.CompressedHE80.n_subcarriers == 996
|
|
assert BfldKind.CompressedHE160.n_subcarriers == 1992
|
|
|
|
|
|
def test_bfld_kind_n_subcarriers_ht() -> None:
|
|
assert BfldKind.UncompressedHT20.n_subcarriers == 52
|
|
assert BfldKind.UncompressedHT40.n_subcarriers == 108
|
|
|
|
|
|
def test_bfld_kind_bandwidth_mhz() -> None:
|
|
assert BfldKind.CompressedHE20.bandwidth_mhz == 20
|
|
assert BfldKind.CompressedHE40.bandwidth_mhz == 40
|
|
assert BfldKind.CompressedHE80.bandwidth_mhz == 80
|
|
assert BfldKind.CompressedHE160.bandwidth_mhz == 160
|
|
assert BfldKind.UncompressedHT20.bandwidth_mhz == 20
|
|
assert BfldKind.UncompressedHT40.bandwidth_mhz == 40
|
|
|
|
|
|
def test_bfld_kind_is_he_flag() -> None:
|
|
assert BfldKind.CompressedHE20.is_he is True
|
|
assert BfldKind.CompressedHE160.is_he is True
|
|
assert BfldKind.UncompressedHT20.is_he is False
|
|
assert BfldKind.UncompressedHT40.is_he is False
|
|
|
|
|
|
def test_bfld_kind_repr() -> None:
|
|
r = repr(BfldKind.CompressedHE80)
|
|
assert "BfldKind" in r and "CompressedHE80" in r
|
|
|
|
|
|
# ─── BfldFrame construction ──────────────────────────────────────────
|
|
|
|
|
|
def _make_matrix(n_rows: int, n_cols: int, n_subcarriers: int) -> np.ndarray:
|
|
"""Synthetic feedback matrix with non-trivial amplitudes so the
|
|
mean_amplitude getter has something to chew on."""
|
|
rng = np.random.default_rng(seed=42)
|
|
real = rng.standard_normal((n_rows, n_cols, n_subcarriers)).astype(np.float64)
|
|
imag = rng.standard_normal((n_rows, n_cols, n_subcarriers)).astype(np.float64)
|
|
return (real + 1j * imag).astype(np.complex128)
|
|
|
|
|
|
def test_bfld_frame_he80_happy_path() -> None:
|
|
fb = _make_matrix(2, 1, 996)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=1234,
|
|
sounding_index=42,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
assert frame.timestamp_ms == 1234
|
|
assert frame.sounding_index == 42
|
|
assert frame.sta_mac == "aa:bb:cc:dd:ee:ff"
|
|
assert frame.kind == BfldKind.CompressedHE80
|
|
assert frame.n_rows == 2
|
|
assert frame.n_cols == 1
|
|
assert frame.n_subcarriers == 996
|
|
|
|
|
|
def test_bfld_frame_he160_2x2() -> None:
|
|
fb = _make_matrix(2, 2, 1992)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="00:00:00:00:00:00",
|
|
kind=BfldKind.CompressedHE160,
|
|
feedback_matrix=fb,
|
|
)
|
|
assert frame.n_rows == 2
|
|
assert frame.n_cols == 2
|
|
assert frame.n_subcarriers == 1992
|
|
|
|
|
|
def test_bfld_frame_ht20_legacy_path() -> None:
|
|
fb = _make_matrix(1, 1, 52)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.UncompressedHT20,
|
|
feedback_matrix=fb,
|
|
)
|
|
assert frame.kind == BfldKind.UncompressedHT20
|
|
assert frame.n_subcarriers == 52
|
|
|
|
|
|
def test_bfld_frame_subcarrier_dim_mismatch_raises() -> None:
|
|
# HE80 requires 996 subcarriers; pass 64 → ValueError.
|
|
bad = _make_matrix(2, 1, 64)
|
|
with pytest.raises(ValueError, match="subcarrier"):
|
|
BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=bad,
|
|
)
|
|
|
|
|
|
def test_bfld_frame_mean_amplitude_is_finite() -> None:
|
|
fb = _make_matrix(2, 1, 996)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
amp = frame.mean_amplitude
|
|
assert math.isfinite(amp) and amp > 0.0
|
|
|
|
|
|
def test_bfld_frame_numpy_roundtrip_preserves_shape() -> None:
|
|
fb = _make_matrix(2, 1, 996)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
out = frame.feedback_matrix()
|
|
assert out.shape == (2, 1, 996)
|
|
# Roundtrip should be lossless (Complex64 in, Complex64 out).
|
|
assert np.allclose(out, fb.astype(np.complex128))
|
|
|
|
|
|
def test_bfld_frame_repr_is_readable() -> None:
|
|
fb = _make_matrix(2, 1, 996)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
r = repr(frame)
|
|
assert "BfldFrame" in r
|
|
assert "996" in r
|
|
assert "CompressedHE80" in r
|
|
|
|
|
|
# ─── BfldReport ──────────────────────────────────────────────────────
|
|
|
|
|
|
def test_bfld_report_starts_empty() -> None:
|
|
report = BfldReport()
|
|
assert report.n_frames == 0
|
|
assert report.kind is None
|
|
assert report.timestamp_first is None
|
|
assert report.timestamp_last is None
|
|
assert report.coherence_score == 0.0
|
|
|
|
|
|
def test_bfld_report_aggregates_homogeneous_frames() -> None:
|
|
report = BfldReport()
|
|
fb = _make_matrix(2, 1, 996)
|
|
for i in range(5):
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=1000 + i * 100,
|
|
sounding_index=i,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
report.add_frame(frame)
|
|
assert report.n_frames == 5
|
|
assert report.kind == BfldKind.CompressedHE80
|
|
assert report.timestamp_first == 1000
|
|
assert report.timestamp_last == 1400
|
|
# Identical synthetic matrices → near-perfect coherence.
|
|
assert report.coherence_score >= 0.99
|
|
|
|
|
|
def test_bfld_report_rejects_mismatched_kind() -> None:
|
|
report = BfldReport()
|
|
fb_he80 = _make_matrix(2, 1, 996)
|
|
fb_he40 = _make_matrix(2, 1, 484)
|
|
he80 = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb_he80,
|
|
)
|
|
he40 = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE40,
|
|
feedback_matrix=fb_he40,
|
|
)
|
|
report.add_frame(he80)
|
|
with pytest.raises(ValueError, match="kind"):
|
|
report.add_frame(he40)
|
|
|
|
|
|
def test_bfld_report_repr_summarises() -> None:
|
|
report = BfldReport()
|
|
fb = _make_matrix(2, 1, 996)
|
|
frame = BfldFrame.from_compressed_feedback(
|
|
timestamp_ms=0,
|
|
sounding_index=0,
|
|
sta_mac="aa:bb:cc:dd:ee:ff",
|
|
kind=BfldKind.CompressedHE80,
|
|
feedback_matrix=fb,
|
|
)
|
|
report.add_frame(frame)
|
|
r = repr(report)
|
|
assert "BfldReport" in r
|
|
assert "n_frames=1" in r
|
|
|
|
|
|
# ─── Build feature flag ──────────────────────────────────────────────
|
|
|
|
|
|
def test_p3_5_bfld_in_build_features() -> None:
|
|
assert "p3.5-bfld-bindings" in wifi_densepose.__build_features__
|