"""Быстрые проверки формата управляющих сообщений: protocol/control_messages.py. Перенесено из функциональных проверок Lab041, где эти свойства проверялись только при полном запуске лабораторной. """ from __future__ import annotations import pytest from protocol.control_messages import ( COMMON_SIZE, ControlMessageError, ControlStateMessage, EmergencyAck, EmergencyStop, MessageContext, MessageType, MovementAuthorizeAck, MovementAuthorizeRequest, ResetAck, ResetRequest, RoverSafetyCode, SequenceComparison, SessionHello, SessionReject, SessionRejectReason, SessionStatus, compare_sequence, decode_message, encode_message, ) CONTEXT = MessageContext(11, 22, 33, 44) ALL_MESSAGES = ( SessionHello(CONTEXT, True, 101), SessionStatus(CONTEXT, RoverSafetyCode.ROVER_SESSION_SYNCED_SAFE, False, 0), MovementAuthorizeRequest(CONTEXT, 201), MovementAuthorizeAck(CONTEXT, 201, True), ControlStateMessage(CONTEXT, 1.25, -0.5, False, True), EmergencyStop(CONTEXT, 301), EmergencyAck(CONTEXT, 301), ResetRequest(CONTEXT, 301, 401, 0.0, False), ResetAck(CONTEXT, 301, 401, True, SessionRejectReason.ACCEPTED), SessionReject(CONTEXT, MessageType.CONTROL_STATE, SessionRejectReason.CONTROL_EPOCH), ) @pytest.mark.parametrize("message", ALL_MESSAGES, ids=lambda m: type(m).__name__) def test_roundtrip_is_exact(message) -> None: assert decode_message(encode_message(message)) == message def test_every_message_type_is_covered() -> None: """Если добавится тип сообщения, этот тест обязан упасть.""" assert len(ALL_MESSAGES) == len(MessageType) @pytest.mark.parametrize("message", ALL_MESSAGES, ids=lambda m: type(m).__name__) def test_encoding_starts_with_the_common_header(message) -> None: encoded = encode_message(message) assert len(encoded) >= COMMON_SIZE assert encoded[0] == 1 def test_wrong_version_is_rejected() -> None: payload = bytearray(encode_message(SessionHello(CONTEXT, False, 0))) payload[0] = 2 with pytest.raises(ControlMessageError): decode_message(bytes(payload)) def test_unknown_type_is_rejected() -> None: payload = bytearray(encode_message(SessionHello(CONTEXT, False, 0))) payload[1] = 255 with pytest.raises(ControlMessageError): decode_message(bytes(payload)) def test_truncated_message_is_rejected() -> None: payload = encode_message(SessionHello(CONTEXT, False, 0)) for cut in (0, 1, COMMON_SIZE - 1, len(payload) - 1): with pytest.raises(ControlMessageError): decode_message(payload[:cut]) # ------------------------------------------------------- диапазоны команд движения @pytest.mark.parametrize( "speed,turn,braking,permitted", [ (15.1, 0.0, False, True), # выше предела (-0.1, 0.0, False, True), # отрицательная скорость (-5.0, 0.0, False, True), (1.0, 1.1, False, True), # поворот вне диапазона (1.0, -1.1, False, True), (1.0, 0.0, True, False), # торможение при ненулевой скорости (1.0, 0.0, False, False), # движение без разрешения ], ) def test_invalid_control_state_is_rejected(speed, turn, braking, permitted) -> None: with pytest.raises(ControlMessageError): encode_message(ControlStateMessage(CONTEXT, speed, turn, braking, permitted)) @pytest.mark.parametrize( "speed,turn,braking,permitted", [ (0.0, 0.0, True, False), (15.0, 0.0, False, True), (0.0, 0.0, False, False), (7.5, 1.0, False, True), (7.5, -1.0, False, True), ], ) def test_valid_control_state_is_accepted(speed, turn, braking, permitted) -> None: message = ControlStateMessage(CONTEXT, speed, turn, braking, permitted) assert decode_message(encode_message(message)) == message @pytest.mark.parametrize("bad", [float("nan"), float("inf"), float("-inf")]) def test_non_finite_speed_is_rejected(bad: float) -> None: with pytest.raises(ControlMessageError): encode_message(ControlStateMessage(CONTEXT, bad, 0.0, False, True)) # ------------------------------------------------------- сравнение номеров сообщений def test_sequence_comparison_basic_order() -> None: assert compare_sequence(5, 4) is SequenceComparison.NEWER assert compare_sequence(4, 5) is SequenceComparison.NOT_NEWER assert compare_sequence(4, 4) is SequenceComparison.NOT_NEWER def test_sequence_comparison_wraps_at_32_bits() -> None: """Переход через 0xFFFFFFFF обязан считаться новым, а не откатом.""" assert compare_sequence(0, 0xFFFFFFFF) is SequenceComparison.NEWER assert compare_sequence(1, 0xFFFFFFFE) is SequenceComparison.NEWER def test_ambiguous_half_range_is_rejected() -> None: """Разность ровно 2^31 неотличима от отката и должна отклоняться.""" assert compare_sequence(0x80000000, 0) is SequenceComparison.AMBIGUOUS assert compare_sequence(0, 0x80000000) is SequenceComparison.AMBIGUOUS def test_just_below_the_ambiguous_boundary_is_newer() -> None: assert compare_sequence(0x7FFFFFFF, 0) is SequenceComparison.NEWER