Skip to content

Commit ee9859c

Browse files
test: improve automated regression test coverage across multiple modules
- Add tests for BinLogSelectionWidgets, VehicleDirectorySelectionWidgets error paths, and update_directory_display edge cases - Add tests for _get_ttk_label_color TclError branch, RichText with explicit colors and with safe_font_nametofont returning None - Add tests for derived parameters filtering (fc_keys filter, file-only params, offline mode) and connection rename no-op/conflict cases - Add tests for send_command_and_wait_ack, stop_all_motors, request_periodic_battery_status without connection; failure branches for reset_all_parameters, test_motors_in_sequence, stop_all_motors; and IN_PROGRESS with zero progress - Add tests for _download_params_via_mavlink and _download_params_via_mavftp with None master, timeout, exception, and progress callback - Add tests for VehicleComponents template loading error handling (FileNotFoundError, OSError, RuntimeError for both system and user), template merging edge cases, and wipe_component_info with None data - Fix button-interaction tests in about popup window to use correct mock path - Add tuning_report.csv to .gitignore Agent-Logs-Url: https://github.com/ArduPilot/MethodicConfigurator/sessions/20139120-bd7b-4efe-8256-126ef58e373b Co-authored-by: amilcarlucas <24453563+amilcarlucas@users.noreply.github.com>
1 parent 430b549 commit ee9859c

10 files changed

Lines changed: 1790 additions & 28 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ test.xml
2323
sitl/arducopter
2424
sitl/firmware-version.txt
2525
sitl/git-version.txt
26+
27+
tuning_report.csv

tests/test_backend_flightcontroller_commands.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,3 +954,221 @@ def test_send_command_exception_in_send(self) -> None:
954954
# Then
955955
assert success is False
956956
assert "failed to send command" in error.lower()
957+
958+
959+
class TestFlightControllerCommandsMissingConnectionBranches:
960+
"""Tests for commands that need a connection but master is None."""
961+
962+
def test_send_command_and_wait_ack_fails_without_connection(self) -> None:
963+
"""
964+
send_command_and_wait_ack fails gracefully when master is None.
965+
966+
GIVEN: No flight controller connection (master is None)
967+
WHEN: send_command_and_wait_ack is called
968+
THEN: False should be returned with appropriate error message
969+
AND: No exceptions should be raised
970+
"""
971+
mock_conn_mgr = Mock()
972+
mock_conn_mgr.master = None
973+
mock_params_mgr = Mock()
974+
975+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
976+
977+
success, error = commands_mgr.send_command_and_wait_ack(command=999, timeout=0.5)
978+
979+
assert success is False
980+
assert "connection" in error.lower()
981+
982+
def test_stop_all_motors_fails_without_connection(self) -> None:
983+
"""
984+
stop_all_motors fails gracefully when master is None.
985+
986+
GIVEN: No flight controller connection
987+
WHEN: stop_all_motors is called
988+
THEN: False should be returned with error message
989+
"""
990+
mock_conn_mgr = Mock()
991+
mock_conn_mgr.master = None
992+
mock_params_mgr = Mock()
993+
994+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
995+
996+
success, error = commands_mgr.stop_all_motors()
997+
998+
assert success is False
999+
assert "connection" in error.lower()
1000+
1001+
def test_request_periodic_battery_status_fails_without_connection(self) -> None:
1002+
"""
1003+
request_periodic_battery_status fails gracefully when master is None.
1004+
1005+
GIVEN: No flight controller connection
1006+
WHEN: request_periodic_battery_status is called
1007+
THEN: False should be returned with error message
1008+
"""
1009+
mock_conn_mgr = Mock()
1010+
mock_conn_mgr.master = None
1011+
mock_params_mgr = Mock()
1012+
1013+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1014+
1015+
success, error = commands_mgr.request_periodic_battery_status()
1016+
1017+
assert success is False
1018+
assert "connection" in error.lower()
1019+
1020+
1021+
class TestFlightControllerCommandsFailureBranches:
1022+
"""Tests for failure branches in command methods."""
1023+
1024+
def _make_commands_mgr_with_ack(self, result_code: int) -> "FlightControllerCommands":
1025+
"""Create a commands manager that returns a given ACK result code."""
1026+
mock_master = MagicMock()
1027+
mock_master.target_system = 1
1028+
mock_master.target_component = 1
1029+
1030+
mock_ack = MagicMock()
1031+
mock_ack.command = mavutil.mavlink.MAV_CMD_DO_MOTOR_TEST
1032+
mock_ack.result = result_code
1033+
1034+
mock_master.recv_match.return_value = mock_ack
1035+
mock_conn_mgr = Mock()
1036+
mock_conn_mgr.master = mock_master
1037+
mock_params_mgr = Mock()
1038+
mock_params_mgr.fc_parameters = {}
1039+
1040+
return FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1041+
1042+
def test_reset_all_parameters_handles_command_failure(self) -> None:
1043+
"""
1044+
reset_all_parameters_to_default handles command failure correctly.
1045+
1046+
GIVEN: Connected flight controller that rejects the parameter reset command
1047+
WHEN: User calls reset_all_parameters_to_default
1048+
THEN: False should be returned with error description
1049+
AND: fc_parameters should NOT be cleared on failure
1050+
"""
1051+
mock_master = MagicMock()
1052+
mock_master.target_system = 1
1053+
mock_master.target_component = 1
1054+
1055+
mock_ack = MagicMock()
1056+
mock_ack.command = mavutil.mavlink.MAV_CMD_PREFLIGHT_STORAGE
1057+
mock_ack.result = mavutil.mavlink.MAV_RESULT_DENIED
1058+
1059+
mock_master.recv_match.return_value = mock_ack
1060+
1061+
mock_conn_mgr = Mock()
1062+
mock_conn_mgr.master = mock_master
1063+
mock_params_mgr = Mock()
1064+
mock_params_mgr.fc_parameters = {"PARAM1": 1.0}
1065+
1066+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1067+
1068+
success, error = commands_mgr.reset_all_parameters_to_default()
1069+
1070+
assert success is False
1071+
assert "failed" in error.lower() or "denied" in error.lower()
1072+
# fc_parameters should NOT be cleared on failure
1073+
assert len(mock_params_mgr.fc_parameters) > 0
1074+
1075+
def test_test_motors_in_sequence_handles_command_failure(self) -> None:
1076+
"""
1077+
test_motors_in_sequence handles command failure correctly.
1078+
1079+
GIVEN: Connected flight controller that rejects the sequential motor test command
1080+
WHEN: User calls test_motors_in_sequence
1081+
THEN: False should be returned with error description
1082+
"""
1083+
mock_master = MagicMock()
1084+
mock_master.target_system = 1
1085+
mock_master.target_component = 1
1086+
1087+
mock_ack = MagicMock()
1088+
mock_ack.command = mavutil.mavlink.MAV_CMD_DO_MOTOR_TEST
1089+
mock_ack.result = mavutil.mavlink.MAV_RESULT_FAILED
1090+
1091+
mock_master.recv_match.return_value = mock_ack
1092+
1093+
mock_conn_mgr = Mock()
1094+
mock_conn_mgr.master = mock_master
1095+
mock_params_mgr = Mock()
1096+
1097+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1098+
1099+
success, error = commands_mgr.test_motors_in_sequence(
1100+
start_motor=1, motor_count=4, throttle_percent=10, timeout_seconds=2
1101+
)
1102+
1103+
assert success is False
1104+
assert "failed" in error.lower()
1105+
1106+
def test_stop_all_motors_handles_command_failure(self) -> None:
1107+
"""
1108+
stop_all_motors handles command failure correctly.
1109+
1110+
GIVEN: Connected flight controller that rejects the stop command
1111+
WHEN: User calls stop_all_motors
1112+
THEN: False should be returned with error description
1113+
"""
1114+
mock_master = MagicMock()
1115+
mock_master.target_system = 1
1116+
mock_master.target_component = 1
1117+
1118+
mock_ack = MagicMock()
1119+
mock_ack.command = mavutil.mavlink.MAV_CMD_DO_MOTOR_TEST
1120+
mock_ack.result = mavutil.mavlink.MAV_RESULT_UNSUPPORTED
1121+
1122+
mock_master.recv_match.return_value = mock_ack
1123+
1124+
mock_conn_mgr = Mock()
1125+
mock_conn_mgr.master = mock_master
1126+
mock_params_mgr = Mock()
1127+
1128+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1129+
1130+
success, error = commands_mgr.stop_all_motors()
1131+
1132+
assert success is False
1133+
assert error # Non-empty error message
1134+
1135+
def test_send_command_handles_in_progress_with_zero_progress(self) -> None:
1136+
"""
1137+
send_command_and_wait_ack handles MAV_RESULT_IN_PROGRESS with zero progress.
1138+
1139+
GIVEN: Flight controller sends IN_PROGRESS with progress=0
1140+
WHEN: send_command_and_wait_ack receives an IN_PROGRESS ACK
1141+
THEN: Processing continues waiting without logging (progress <= 0)
1142+
AND: Eventually times out and returns False
1143+
"""
1144+
mock_master = MagicMock()
1145+
mock_master.target_system = 1
1146+
mock_master.target_component = 1
1147+
1148+
# Return IN_PROGRESS ACK with progress=0 (should NOT log) then timeout
1149+
mock_ack = MagicMock()
1150+
mock_ack.command = 999
1151+
mock_ack.result = mavutil.mavlink.MAV_RESULT_IN_PROGRESS
1152+
mock_ack.progress = 0 # <= 0, so debug logging is skipped
1153+
1154+
call_count = [0]
1155+
1156+
def side_effect_recv_match(*_args, **_kwargs) -> object:
1157+
call_count[0] += 1
1158+
if call_count[0] <= 2:
1159+
return mock_ack
1160+
return None # Stop returning ACK after a couple of calls
1161+
1162+
mock_master.recv_match.side_effect = side_effect_recv_match
1163+
1164+
mock_conn_mgr = Mock()
1165+
mock_conn_mgr.master = mock_master
1166+
mock_params_mgr = Mock()
1167+
1168+
commands_mgr = FlightControllerCommands(params_manager=mock_params_mgr, connection_manager=mock_conn_mgr)
1169+
1170+
# Short timeout so test doesn't hang
1171+
success, _error = commands_mgr.send_command_and_wait_ack(command=999, timeout=0.3)
1172+
1173+
# Should timeout after IN_PROGRESS messages
1174+
assert success is False

0 commit comments

Comments
 (0)