CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/autotest/unittest/extract_param_defaults_unittest.py
Views: 1799
1
#!/usr/bin/env python3
2
3
'''
4
Extracts parameter default values from an ArduPilot .bin log file. Unittests.
5
6
AP_FLAKE8_CLEAN
7
8
Amilcar do Carmo Lucas, IAV GmbH
9
'''
10
11
import unittest
12
from unittest.mock import patch, MagicMock
13
from extract_param_defaults import extract_parameter_default_values, missionplanner_sort, \
14
mavproxy_sort, sort_params, output_params, parse_arguments, \
15
NO_DEFAULT_VALUES_MESSAGE, MAVLINK_SYSID_MAX, MAVLINK_COMPID_MAX
16
17
18
class TestArgParseParameters(unittest.TestCase):
19
def test_command_line_arguments_combinations(self):
20
# Check the 'format' and 'sort' default parameters
21
args = parse_arguments(['dummy.bin'])
22
self.assertEqual(args.format, 'missionplanner')
23
self.assertEqual(args.sort, 'missionplanner')
24
25
# Check the 'format' and 'sort' parameters to see if 'sort' can be explicitly overwritten
26
args = parse_arguments(['-s', 'none', 'dummy.bin'])
27
self.assertEqual(args.format, 'missionplanner')
28
self.assertEqual(args.sort, 'none')
29
30
# Check the 'format' and 'sort' parameters to see if 'sort' can be implicitly overwritten (mavproxy)
31
args = parse_arguments(['-f', 'mavproxy', 'dummy.bin'])
32
self.assertEqual(args.format, 'mavproxy')
33
self.assertEqual(args.sort, 'mavproxy')
34
35
# Check the 'format' and 'sort' parameters to see if 'sort' can be implicitly overwritten (qgcs)
36
args = parse_arguments(['-f', 'qgcs', 'dummy.bin'])
37
self.assertEqual(args.format, 'qgcs')
38
self.assertEqual(args.sort, 'qgcs')
39
40
# Check the 'format' and 'sort' parameters
41
args = parse_arguments(['-f', 'mavproxy', '-s', 'none', 'dummy.bin'])
42
self.assertEqual(args.format, 'mavproxy')
43
self.assertEqual(args.sort, 'none')
44
45
# Assert that a SystemExit is raised when --sysid is used without --format set to qgcs
46
with self.assertRaises(SystemExit):
47
with patch('builtins.print') as mock_print:
48
parse_arguments(['-f', 'mavproxy', '-i', '7', 'dummy.bin'])
49
mock_print.assert_called_once_with("--sysid parameter is only relevant if --format is qgcs")
50
51
# Assert that a SystemExit is raised when --compid is used without --format set to qgcs
52
with self.assertRaises(SystemExit):
53
with patch('builtins.print') as mock_print:
54
parse_arguments(['-f', 'missionplanner', '-c', '3', 'dummy.bin'])
55
mock_print.assert_called_once_with("--compid parameter is only relevant if --format is qgcs")
56
57
# Assert that a valid sysid and compid are parsed correctly
58
args = parse_arguments(['-f', 'qgcs', '-i', '7', '-c', '3', 'dummy.bin'])
59
self.assertEqual(args.format, 'qgcs')
60
self.assertEqual(args.sort, 'qgcs')
61
self.assertEqual(args.sysid, 7)
62
self.assertEqual(args.compid, 3)
63
64
65
class TestExtractParameterDefaultValues(unittest.TestCase):
66
67
@patch('extract_param_defaults.mavutil.mavlink_connection')
68
def test_logfile_does_not_exist(self, mock_mavlink_connection):
69
# Mock the mavlink connection to raise an exception
70
mock_mavlink_connection.side_effect = Exception("Test exception")
71
72
# Call the function with a dummy logfile path
73
with self.assertRaises(SystemExit) as cm:
74
extract_parameter_default_values('dummy.bin')
75
76
# Check the error message
77
self.assertEqual(str(cm.exception), "Error opening the dummy.bin logfile: Test exception")
78
79
@patch('extract_param_defaults.mavutil.mavlink_connection')
80
def test_extract_parameter_default_values(self, mock_mavlink_connection):
81
# Mock the mavlink connection and the messages it returns
82
mock_mlog = MagicMock()
83
mock_mavlink_connection.return_value = mock_mlog
84
mock_mlog.recv_match.side_effect = [
85
MagicMock(Name='PARAM1', Default=1.1),
86
MagicMock(Name='PARAM2', Default=2.0),
87
None # End of messages
88
]
89
90
# Call the function with a dummy logfile path
91
defaults = extract_parameter_default_values('dummy.bin')
92
93
# Check if the defaults dictionary contains the correct parameters and values
94
self.assertEqual(defaults, {'PARAM1': 1.1, 'PARAM2': 2.0})
95
96
@patch('extract_param_defaults.mavutil.mavlink_connection')
97
def test_no_parameters(self, mock_mavlink_connection):
98
# Mock the mavlink connection to return no parameter messages
99
mock_mlog = MagicMock()
100
mock_mavlink_connection.return_value = mock_mlog
101
mock_mlog.recv_match.return_value = None # No PARM messages
102
103
# Call the function with a dummy logfile path and assert SystemExit is raised with the correct message
104
with self.assertRaises(SystemExit) as cm:
105
extract_parameter_default_values('dummy.bin')
106
self.assertEqual(str(cm.exception), NO_DEFAULT_VALUES_MESSAGE)
107
108
@patch('extract_param_defaults.mavutil.mavlink_connection')
109
def test_no_parameter_defaults(self, mock_mavlink_connection):
110
# Mock the mavlink connection to simulate no parameter default values in the .bin file
111
mock_mlog = MagicMock()
112
mock_mavlink_connection.return_value = mock_mlog
113
mock_mlog.recv_match.return_value = None # No PARM messages
114
115
# Call the function with a dummy logfile path and assert SystemExit is raised with the correct message
116
with self.assertRaises(SystemExit) as cm:
117
extract_parameter_default_values('dummy.bin')
118
self.assertEqual(str(cm.exception), NO_DEFAULT_VALUES_MESSAGE)
119
120
@patch('extract_param_defaults.mavutil.mavlink_connection')
121
def test_invalid_parameter_name(self, mock_mavlink_connection):
122
# Mock the mavlink connection to simulate an invalid parameter name
123
mock_mlog = MagicMock()
124
mock_mavlink_connection.return_value = mock_mlog
125
mock_mlog.recv_match.return_value = MagicMock(Name='INVALID_NAME%', Default=1.0)
126
127
# Call the function with a dummy logfile path
128
with self.assertRaises(SystemExit):
129
extract_parameter_default_values('dummy.bin')
130
131
@patch('extract_param_defaults.mavutil.mavlink_connection')
132
def test_long_parameter_name(self, mock_mavlink_connection):
133
# Mock the mavlink connection to simulate a too long parameter name
134
mock_mlog = MagicMock()
135
mock_mavlink_connection.return_value = mock_mlog
136
mock_mlog.recv_match.return_value = MagicMock(Name='TOO_LONG_PARAMETER_NAME', Default=1.0)
137
138
# Call the function with a dummy logfile path
139
with self.assertRaises(SystemExit):
140
extract_parameter_default_values('dummy.bin')
141
142
143
class TestSortFunctions(unittest.TestCase):
144
def test_missionplanner_sort(self):
145
# Define a list of parameter names
146
params = ['PARAM_GROUP1_PARAM1', 'PARAM_GROUP2_PARAM2', 'PARAM_GROUP1_PARAM2']
147
148
# Sort the parameters using the missionplanner_sort function
149
sorted_params = sorted(params, key=missionplanner_sort)
150
151
# Check if the parameters were sorted correctly
152
self.assertEqual(sorted_params, ['PARAM_GROUP1_PARAM1', 'PARAM_GROUP1_PARAM2', 'PARAM_GROUP2_PARAM2'])
153
154
# Test with a parameter name that doesn't contain an underscore
155
params = ['PARAM1', 'PARAM3', 'PARAM2']
156
sorted_params = sorted(params, key=missionplanner_sort)
157
self.assertEqual(sorted_params, ['PARAM1', 'PARAM2', 'PARAM3'])
158
159
def test_mavproxy_sort(self):
160
# Define a list of parameter names
161
params = ['PARAM_GROUP1_PARAM1', 'PARAM_GROUP2_PARAM2', 'PARAM_GROUP1_PARAM2']
162
163
# Sort the parameters using the mavproxy_sort function
164
sorted_params = sorted(params, key=mavproxy_sort)
165
166
# Check if the parameters were sorted correctly
167
self.assertEqual(sorted_params, ['PARAM_GROUP1_PARAM1', 'PARAM_GROUP1_PARAM2', 'PARAM_GROUP2_PARAM2'])
168
169
# Test with a parameter name that doesn't contain an underscore
170
params = ['PARAM1', 'PARAM3', 'PARAM2']
171
sorted_params = sorted(params, key=mavproxy_sort)
172
self.assertEqual(sorted_params, ['PARAM1', 'PARAM2', 'PARAM3'])
173
174
175
class TestOutputParams(unittest.TestCase):
176
177
@patch('extract_param_defaults.print')
178
def test_output_params(self, mock_print):
179
# Prepare a dummy defaults dictionary
180
defaults = {'PARAM2': 1.0, 'PARAM1': 2.0}
181
182
# Call the function with the dummy dictionary, 'missionplanner' format type
183
output_params(defaults, 'missionplanner')
184
185
# Check if the print function was called with the correct parameters
186
expected_calls = [unittest.mock.call('PARAM2,1'), unittest.mock.call('PARAM1,2')]
187
mock_print.assert_has_calls(expected_calls, any_order=False)
188
189
@patch('extract_param_defaults.print')
190
def test_output_params_missionplanner_non_numeric(self, mock_print):
191
# Prepare a dummy defaults dictionary
192
defaults = {'PARAM1': 'non-numeric'}
193
194
# Call the function with the dummy dictionary, 'missionplanner' format type
195
output_params(defaults, 'missionplanner')
196
197
# Check if the print function was called with the correct parameters
198
expected_calls = [unittest.mock.call('PARAM1,non-numeric')]
199
mock_print.assert_has_calls(expected_calls, any_order=False)
200
201
@patch('extract_param_defaults.print')
202
def test_output_params_mavproxy(self, mock_print):
203
# Prepare a dummy defaults dictionary
204
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0}
205
206
# Call the function with the dummy dictionary, 'mavproxy' format type and 'mavproxy' sort type
207
defaults = sort_params(defaults, 'mavproxy')
208
output_params(defaults, 'mavproxy')
209
210
# Check if the print function was called with the correct parameters
211
expected_calls = [unittest.mock.call("%-15s %.6f" % ('PARAM1', 1.0)),
212
unittest.mock.call("%-15s %.6f" % ('PARAM2', 2.0))]
213
mock_print.assert_has_calls(expected_calls, any_order=False)
214
215
@patch('extract_param_defaults.print')
216
def test_output_params_qgcs(self, mock_print):
217
# Prepare a dummy defaults dictionary
218
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0}
219
220
# Call the function with the dummy dictionary, 'qgcs' format type and 'qgcs' sort type
221
defaults = sort_params(defaults, 'qgcs')
222
output_params(defaults, 'qgcs')
223
224
# Check if the print function was called with the correct parameters
225
expected_calls = [unittest.mock.call("\n# # Vehicle-Id Component-Id Name Value Type\n"),
226
unittest.mock.call("%u %u %-15s %.6f %u" % (1, 1, 'PARAM1', 1.0, 9)),
227
unittest.mock.call("%u %u %-15s %.6f %u" % (1, 1, 'PARAM2', 2.0, 9))]
228
mock_print.assert_has_calls(expected_calls, any_order=False)
229
230
@patch('extract_param_defaults.print')
231
def test_output_params_qgcs_2_4(self, mock_print):
232
# Prepare a dummy defaults dictionary
233
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0}
234
235
# Call the function with the dummy dictionary, 'qgcs' format type and 'qgcs' sort type
236
defaults = sort_params(defaults, 'qgcs')
237
output_params(defaults, 'qgcs', 2, 4)
238
239
# Check if the print function was called with the correct parameters
240
expected_calls = [unittest.mock.call("\n# # Vehicle-Id Component-Id Name Value Type\n"),
241
unittest.mock.call("%u %u %-15s %.6f %u" % (2, 4, 'PARAM1', 1.0, 9)),
242
unittest.mock.call("%u %u %-15s %.6f %u" % (2, 4, 'PARAM2', 2.0, 9))]
243
mock_print.assert_has_calls(expected_calls, any_order=False)
244
245
@patch('extract_param_defaults.print')
246
def test_output_params_qgcs_SYSID_THISMAV(self, mock_print):
247
# Prepare a dummy defaults dictionary
248
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0, 'SYSID_THISMAV': 3.0}
249
250
# Call the function with the dummy dictionary, 'qgcs' format type and 'qgcs' sort type
251
defaults = sort_params(defaults, 'qgcs')
252
output_params(defaults, 'qgcs', -1, 7)
253
254
# Check if the print function was called with the correct parameters
255
expected_calls = [unittest.mock.call("\n# # Vehicle-Id Component-Id Name Value Type\n"),
256
unittest.mock.call("%u %u %-15s %.6f %u" % (3, 7, 'PARAM1', 1.0, 9)),
257
unittest.mock.call("%u %u %-15s %.6f %u" % (3, 7, 'PARAM2', 2.0, 9)),
258
unittest.mock.call("%u %u %-15s %.6f %u" % (3, 7, 'SYSID_THISMAV', 3.0, 9))]
259
mock_print.assert_has_calls(expected_calls, any_order=False)
260
261
@patch('extract_param_defaults.print')
262
def test_output_params_qgcs_SYSID_INVALID(self, mock_print):
263
# Prepare a dummy defaults dictionary
264
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0, 'SYSID_THISMAV': -1.0}
265
266
# Assert that a SystemExit is raised with the correct message when an invalid sysid is used
267
with self.assertRaises(SystemExit) as cm:
268
defaults = sort_params(defaults, 'qgcs')
269
output_params(defaults, 'qgcs', -1, 7)
270
self.assertEqual(str(cm.exception), "Invalid system ID parameter -1 must not be negative")
271
272
# Assert that a SystemExit is raised with the correct message when an invalid sysid is used
273
with self.assertRaises(SystemExit) as cm:
274
defaults = sort_params(defaults, 'qgcs')
275
output_params(defaults, 'qgcs', MAVLINK_SYSID_MAX+2, 7)
276
self.assertEqual(str(cm.exception), f"Invalid system ID parameter 16777218 must be smaller than {MAVLINK_SYSID_MAX}")
277
278
@patch('extract_param_defaults.print')
279
def test_output_params_qgcs_COMPID_INVALID(self, mock_print):
280
# Prepare a dummy defaults dictionary
281
defaults = {'PARAM2': 2.0, 'PARAM1': 1.0}
282
283
# Assert that a SystemExit is raised with the correct message when an invalid compid is used
284
with self.assertRaises(SystemExit) as cm:
285
defaults = sort_params(defaults, 'qgcs')
286
output_params(defaults, 'qgcs', -1, -3)
287
self.assertEqual(str(cm.exception), "Invalid component ID parameter -3 must not be negative")
288
289
# Assert that a SystemExit is raised with the correct message when an invalid compid is used
290
with self.assertRaises(SystemExit) as cm:
291
defaults = sort_params(defaults, 'qgcs')
292
output_params(defaults, 'qgcs', 1, MAVLINK_COMPID_MAX+3)
293
self.assertEqual(str(cm.exception), f"Invalid component ID parameter 259 must be smaller than {MAVLINK_COMPID_MAX}")
294
295
@patch('extract_param_defaults.print')
296
def test_output_params_integer(self, mock_print):
297
# Prepare a dummy defaults dictionary with an integer value
298
defaults = {'PARAM1': 1.01, 'PARAM2': 2.00}
299
300
# Call the function with the dummy dictionary, 'missionplanner' format type and 'missionplanner' sort type
301
defaults = sort_params(defaults, 'missionplanner')
302
output_params(defaults, 'missionplanner')
303
304
# Check if the print function was called with the correct parameters
305
expected_calls = [unittest.mock.call('PARAM1,1.01'), unittest.mock.call('PARAM2,2')]
306
mock_print.assert_has_calls(expected_calls, any_order=False)
307
308
309
if __name__ == '__main__':
310
unittest.main()
311
312