"""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"))