Files
SDR-Rover/tests/test_lab066_full_packet_rf_retest.py

150 lines
7.0 KiB
Python

"""Lab066: исправленные паузы, строгий software gate и запрет TX."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import subprocess
import pytest
from experiments import lab066_full_packet_rf_retest as lab
from tools.hackrf_process_check import ProcessCheck, ProcessState
def test_fixed_layout_and_operating_point():
waveform, layout = lab.build_waveform()
assert len(lab.plans()) == 16
assert all(len(frame.plan.payload) == 256 for frame in lab.plans())
assert len({frame.plan.payload for frame in lab.plans()}) == 16
assert lab.PACKET_SAMPLES == 92_256
assert lab.INTER_PACKET_GAP_SAMPLES == 40_000
assert lab.NUMBER_OF_GAPS == 15
assert lab.GUARD_SAMPLES == 800_000
assert lab.BASE_TX_SAMPLES == 2_076_096
assert lab.EXPECTED_TX_SAMPLES == 3_676_096
assert len(waveform) == 2 * lab.EXPECTED_TX_SAMPLES
assert [row["packet_index"] for row in layout] == list(range(1, 17))
assert lab.parameters()["tx_vga_db"] == 20
assert lab.parameters()["rx_lna_db"] == lab.parameters()["rx_vga_db"] == 24
def test_guards_and_all_gaps_are_exact_zero():
waveform, layout = lab.build_waveform()
samples = memoryview(waveform)
assert not any(samples[:2 * lab.GUARD_SAMPLES])
assert not any(samples[-2 * lab.GUARD_SAMPLES:])
for row in layout[:-1]:
low = 2 * row["following_gap_start_sample"]
high = low + 2 * row["following_gap_samples"]
assert high - low == 2 * 40_000
assert not any(samples[low:high])
def test_transfer_commands_are_rx_only_or_fixed_tx():
receive = lab.transfer_command("rx.cs8", 800_000)
transmit = lab.transfer_command("tx.cs8", 100, transmit=True)
assert "-r" in receive and "-t" not in receive
assert "-t" in transmit and "-r" not in transmit
assert receive[receive.index("-f") + 1] == "1400000000"
assert receive[receive.index("-l") + 1] == receive[receive.index("-g") + 1] == "24"
assert transmit[transmit.index("-x") + 1] == "20"
def test_prepare_metadata_and_two_strict_roundtrips(tmp_path, monkeypatch):
raw, output = tmp_path / "raw", tmp_path / "processed"
monkeypatch.setattr(lab, "RAW", raw); monkeypatch.setattr(lab, "OUTPUT", output)
metadata = lab.prepare()
assert metadata["tx_samples"] == lab.EXPECTED_TX_SAMPLES
assert metadata["tx_sha256"] == hashlib.sha256((raw / "tx_stream.cs8").read_bytes()).hexdigest()
result = lab.software_gate()
for mode in ("legacy", "t2"):
row = result[mode]
assert row["header_pass"] == row["fec_pass"] == row["crc_pass"] == 16
assert row["packet_output"] == row["exact_payload_match"] == 16
assert row["packet_indexes"] == list(range(1, 17))
assert row["exact_ordered_payloads"] and row["passed"]
assert row["fallback_between_modes"] is False
assert result["t2"]["t2_state_scope"] == "NEW_PREAMBLE_ESTIMATE_PER_PACKET"
assert result["t2"]["t2_state_reset_evidence"][
"tracking_runs_per_accepted_packet"
] == [2] * 16
assert result["t2_state_reset_verified_by_fresh_preamble_per_packet"] is True
@pytest.mark.parametrize("failure", [None, "clock", "clipping", "strong", "overrun"])
def test_preflight_never_transmits_and_fails_closed(tmp_path, monkeypatch, failure):
raw, output = tmp_path / "raw", tmp_path / "processed"
raw.mkdir(); output.mkdir()
monkeypatch.setattr(lab, "RAW", raw); monkeypatch.setattr(lab, "OUTPUT", output)
(raw / "tx_stream.cs8").write_bytes(b"test")
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "TEST"})
(output / "software_gate.json").write_text('{"passed": true, "tx_sha256": "TEST"}')
monkeypatch.setattr(lab.lab054.phy.lab045, "_verify_device_inventory", lambda runner: {"passed": True})
def clock(point, runner, guard, **kwargs):
return {"passed": failure != "clock", "master_reg29": 0x26,
"slave_reg29": 0x26, "slave_clkin_status": "DETECTED"}
def runner(command):
assert "-t" not in command
if "-r" in command:
value = 127 if failure == "clipping" else (110 if failure == "strong" else 1)
Path(command[command.index("-r") + 1]).write_bytes(bytes([value, value]) * lab.PREFLIGHT_SAMPLES)
log = "1 RX overruns\n0 RX overruns" if failure == "overrun" else "0 RX overruns"
return subprocess.CompletedProcess(command, 0, log, "")
monkeypatch.setattr(lab.lab054, "disable_clock_outputs", lambda runner: [])
kwargs = dict(runner=runner, clock_checker=clock,
process_checker=lambda: ProcessCheck(ProcessState.CLEAR, ()),
sleep_function=lambda _: None)
if failure:
with pytest.raises(RuntimeError): lab.preflight(**kwargs)
else:
assert lab.preflight(**kwargs)["passed"]
record = json.loads((raw / "preflight_result.json").read_text())
assert record["rf_tx_count"] == 0
if failure == "overrun":
assert record["rx_counter_summary"]["maximum_count"] == 1
def test_capture_refuses_without_authorization_before_hardware(tmp_path, monkeypatch):
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
with pytest.raises(PermissionError):
lab.capture_once(authorization=False, runner=lambda command: pytest.fail("hardware"))
assert not (tmp_path / "hardware_attempt.json").exists()
def test_future_analysis_declares_strict_modes_and_physical_comparison():
source = Path(lab.__file__).read_text(encoding="utf-8")
assert 'decode("legacy", rx_path, diagnostics=True)' in source
assert 'decode("t2", rx_path, diagnostics=True)' in source
assert "physical_packet_diagnostics(tx, rx, location)" in source
assert "if not strict_runs_complete" in source
assert '["Lab056 B", "Lab062", "Lab066"]' in source
def test_future_analysis_rejects_capture_from_another_configuration(tmp_path, monkeypatch):
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
rx = tmp_path / "rx_stream.cs8"; rx.write_bytes(b"\x00\x00")
capture = {
"lab": "Lab066", "rf_tx_count": 1, "automatic_tx_repeats": 0,
"point": {}, "parameters": {}, "tx_sha256": "WRONG",
"rx_sha256": lab.sha256(rx),
}
(tmp_path / "capture_result.json").write_text(json.dumps(capture))
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "EXPECTED"})
monkeypatch.setattr(lab, "decode", lambda *args, **kwargs: pytest.fail("decode"))
with pytest.raises(RuntimeError, match="подтверждённой"):
lab.analyze_saved_capture()
def test_second_preflight_is_refused_before_hardware(tmp_path, monkeypatch):
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
(tmp_path / "preflight_attempt.json").write_text("{}")
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "TEST"})
(tmp_path / "software_gate.json").write_text('{"passed": true, "tx_sha256": "TEST"}')
with pytest.raises(FileExistsError):
lab.preflight(runner=lambda command: pytest.fail("hardware"))