Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/Tools/autotest/unittest/annotate_params_unittest.py
Views: 1799
#!/usr/bin/env python312'''3These are the unit tests for the python script that fetches online ArduPilot4parameter documentation (if not cached) and adds it to the specified file or5to all *.param and *.parm files in the specified directory.67AP_FLAKE8_CLEAN89Author: Amilcar do Carmo Lucas, IAV GmbH10'''1112import tempfile13from unittest.mock import patch, mock_open14import os15import unittest16import xml.etree.ElementTree as ET17import requests18import mock19from annotate_params import get_xml_data, remove_prefix, split_into_lines, create_doc_dict, \20format_columns, update_parameter_documentation, print_read_only_params, \21BASE_URL, PARAM_DEFINITION_XML_FILE222324class TestParamDocsUpdate(unittest.TestCase):2526def setUp(self):27# Create a temporary directory28self.temp_dir = tempfile.mkdtemp()2930# Create a temporary file31self.temp_file = tempfile.NamedTemporaryFile(delete=False)3233# Create a dictionary of parameter documentation34self.doc_dict = {35"PARAM1": {36"humanName": "Param 1",37"documentation": ["Documentation for Param 1"],38"fields": {"Field1": "Value1", "Field2": "Value2"},39"values": {"Code1": "Value1", "Code2": "Value2"}40},41"PARAM2": {42"humanName": "Param 2",43"documentation": ["Documentation for Param 2"],44"fields": {"Field3": "Value3", "Field4": "Value4"},45"values": {"Code3": "Value3", "Code4": "Value4"}46},47"PARAM_1": {48"humanName": "Param _ 1",49"documentation": ["Documentation for Param_1"],50"fields": {"Field_1": "Value_1", "Field_2": "Value_2"},51"values": {"Code_1": "Value_1", "Code_2": "Value_2"}52},53}5455@patch('builtins.open', new_callable=mock_open, read_data='<root></root>')56@patch('os.path.isfile')57def test_get_xml_data_local_file(self, mock_isfile, mock_open):58# Mock the isfile function to return True59mock_isfile.return_value = True6061# Call the function with a local file62result = get_xml_data("/path/to/local/file/", ".", "test.xml")6364# Check the result65self.assertIsInstance(result, ET.Element)6667# Assert that the file was opened correctly68mock_open.assert_called_once_with('./test.xml', 'r', encoding='utf-8')6970@patch('requests.get')71def test_get_xml_data_remote_file(self, mock_get):72# Mock the response73mock_get.return_value.status_code = 20074mock_get.return_value.text = "<root></root>"7576# Remove the test.xml file if it exists77try:78os.remove("test.xml")79except FileNotFoundError:80pass8182# Call the function with a remote file83result = get_xml_data("http://example.com/", ".", "test.xml")8485# Check the result86self.assertIsInstance(result, ET.Element)8788# Assert that the requests.get function was called once89mock_get.assert_called_once_with("http://example.com/test.xml", timeout=5)9091@patch('os.path.isfile')92def test_get_xml_data_script_dir_file(self, mock_isfile):93# Mock the isfile function to return False for the current directory and True for the script directory94def side_effect(filename):95return True96mock_isfile.side_effect = side_effect9798# Mock the open function to return a dummy XML string99mock_open = mock.mock_open(read_data='<root></root>')100with patch('builtins.open', mock_open):101# Call the function with a filename that exists in the script directory102result = get_xml_data(BASE_URL, ".", PARAM_DEFINITION_XML_FILE)103104# Check the result105self.assertIsInstance(result, ET.Element)106107# Assert that the file was opened correctly108mock_open.assert_called_once_with(os.path.join('.', PARAM_DEFINITION_XML_FILE), 'r', encoding='utf-8')109110def test_get_xml_data_no_requests_package(self):111# Temporarily remove the requests module112with patch.dict('sys.modules', {'requests': None}):113114# Remove the test.xml file if it exists115try:116os.remove("test.xml")117except FileNotFoundError:118pass119120# Call the function with a remote file121with self.assertRaises(SystemExit):122get_xml_data("http://example.com/", ".", "test.xml")123124@patch('requests.get')125def test_get_xml_data_request_failure(self, mock_get):126# Mock the response127mock_get.side_effect = requests.exceptions.RequestException128129# Remove the test.xml file if it exists130try:131os.remove("test.xml")132except FileNotFoundError:133pass134135# Call the function with a remote file136with self.assertRaises(SystemExit):137get_xml_data("http://example.com/", ".", "test.xml")138139@patch('requests.get')140def test_get_xml_data_valid_xml(self, mock_get):141# Mock the response142mock_get.return_value.status_code = 200143mock_get.return_value.text = "<root></root>"144145# Call the function with a remote file146result = get_xml_data("http://example.com/", ".", "test.xml")147148# Check the result149self.assertIsInstance(result, ET.Element)150151@patch('requests.get')152def test_get_xml_data_invalid_xml(self, mock_get):153# Mock the response154mock_get.return_value.status_code = 200155mock_get.return_value.text = "<root><invalid></root>"156157# Remove the test.xml file if it exists158try:159os.remove("test.xml")160except FileNotFoundError:161pass162163# Call the function with a remote file164with self.assertRaises(ET.ParseError):165get_xml_data("http://example.com/", ".", "test.xml")166167@patch('requests.get')168@patch('os.path.isfile')169def test_get_xml_data_missing_file(self, mock_isfile, mock_get):170# Mock the isfile function to return False171mock_isfile.return_value = False172# Mock the requests.get call to raise FileNotFoundError173mock_get.side_effect = FileNotFoundError174175# Remove the test.xml file if it exists176try:177os.remove("test.xml")178except FileNotFoundError:179pass180181# Call the function with a local file182with self.assertRaises(FileNotFoundError):183get_xml_data("/path/to/local/file/", ".", "test.xml")184185@patch('requests.get')186def test_get_xml_data_network_issue(self, mock_get):187# Mock the response188mock_get.side_effect = requests.exceptions.ConnectionError189190# Call the function with a remote file191with self.assertRaises(SystemExit):192get_xml_data("http://example.com/", ".", "test.xml")193194def test_remove_prefix(self):195# Test case 1: Normal operation196self.assertEqual(remove_prefix("prefix_test", "prefix_"), "test")197198# Test case 2: Prefix not present199self.assertEqual(remove_prefix("test", "prefix_"), "test")200201# Test case 3: Empty string202self.assertEqual(remove_prefix("", "prefix_"), "")203204def test_split_into_lines(self):205# Test case 1: Normal operation206string_to_split = "This is a test string. It should be split into several lines."207maximum_line_length = 12208expected_output = ["This is a", "test string.", "It should be", "split into", "several", "lines."]209self.assertEqual(split_into_lines(string_to_split, maximum_line_length), expected_output)210211# Test case 2: String shorter than maximum line length212string_to_split = "Short"213maximum_line_length = 10214expected_output = ["Short"]215self.assertEqual(split_into_lines(string_to_split, maximum_line_length), expected_output)216217# Test case 3: Empty string218string_to_split = ""219maximum_line_length = 10220expected_output = []221self.assertEqual(split_into_lines(string_to_split, maximum_line_length), expected_output)222223def test_create_doc_dict(self):224# Mock XML data225xml_data = '''226<root>227<param name="PARAM1" humanName="Param 1" documentation="Documentation for Param 1">228<field name="Field1">Value1</field>229<field name="Field2">Value2</field>230<values>231<value code="Code1">Value1</value>232<value code="Code2">Value2</value>233</values>234</param>235<param name="PARAM2" humanName="Param 2" documentation="Documentation for Param 2">236<field name="Units">m/s</field>237<field name="UnitText">meters per second</field>238<values>239<value code="Code3">Value3</value>240<value code="Code4">Value4</value>241</values>242</param>243</root>244'''245root = ET.fromstring(xml_data)246247# Expected output248expected_output = {249"PARAM1": {250"humanName": "Param 1",251"documentation": ["Documentation for Param 1"],252"fields": {"Field1": "Value1", "Field2": "Value2"},253"values": {"Code1": "Value1", "Code2": "Value2"}254},255"PARAM2": {256"humanName": "Param 2",257"documentation": ["Documentation for Param 2"],258"fields": {"Units": "m/s (meters per second)"},259"values": {"Code3": "Value3", "Code4": "Value4"}260}261}262263# Call the function with the mock XML data264result = create_doc_dict(root, "VehicleType")265266# Check the result267self.assertEqual(result, expected_output)268269def test_format_columns(self):270# Define the input271values = {272"Key1": "Value1",273"Key2": "Value2",274"Key3": "Value3",275"Key4": "Value4",276"Key5": "Value5",277"Key6": "Value6",278"Key7": "Value7",279"Key8": "Value8",280"Key9": "Value9",281"Key10": "Value10",282"Key11": "Value11",283"Key12": "Value12",284}285286# Define the expected output287expected_output = [288'Key1: Value1 Key7: Value7',289'Key2: Value2 Key8: Value8',290'Key3: Value3 Key9: Value9',291'Key4: Value4 Key10: Value10',292'Key5: Value5 Key11: Value11',293'Key6: Value6 Key12: Value12',294]295296# Call the function with the input297result = format_columns(values)298299# Check the result300self.assertEqual(result, expected_output)301302self.assertEqual(format_columns({}), [])303304def test_update_parameter_documentation(self):305# Write some initial content to the temporary file306with open(self.temp_file.name, "w", encoding="utf-8") as file:307file.write("PARAM1 100\n")308309# Call the function with the temporary file310update_parameter_documentation(self.doc_dict, self.temp_file.name)311312# Read the updated content from the temporary file313with open(self.temp_file.name, "r", encoding="utf-8") as file:314updated_content = file.read()315316# Check if the file has been updated correctly317self.assertIn("Param 1", updated_content)318self.assertIn("Documentation for Param 1", updated_content)319self.assertIn("Field1: Value1", updated_content)320self.assertIn("Field2: Value2", updated_content)321self.assertIn("Code1: Value1", updated_content)322self.assertIn("Code2: Value2", updated_content)323324def test_update_parameter_documentation_sorting_none(self):325# Write some initial content to the temporary file326# With stray leading and trailing whitespaces327with open(self.temp_file.name, "w", encoding="utf-8") as file:328file.write("PARAM2 100\n PARAM_1 100 \nPARAM3 3\nPARAM4 4\nPARAM5 5\nPARAM1 100\n")329330# Call the function with the temporary file331update_parameter_documentation(self.doc_dict, self.temp_file.name)332333# Read the updated content from the temporary file334with open(self.temp_file.name, "r", encoding="utf-8") as file:335updated_content = file.read()336337expected_content = '''# Param 2338# Documentation for Param 2339# Field3: Value3340# Field4: Value4341# Code3: Value3342# Code4: Value4343PARAM2 100344345# Param _ 1346# Documentation for Param_1347# Field_1: Value_1348# Field_2: Value_2349# Code_1: Value_1350# Code_2: Value_2351PARAM_1 100352PARAM3 3353PARAM4 4354PARAM5 5355356# Param 1357# Documentation for Param 1358# Field1: Value1359# Field2: Value2360# Code1: Value1361# Code2: Value2362PARAM1 100363'''364self.assertEqual(updated_content, expected_content)365366def test_update_parameter_documentation_sorting_missionplanner(self):367# Write some initial content to the temporary file368with open(self.temp_file.name, "w", encoding="utf-8") as file:369file.write("PARAM2 100 # ignore, me\nPARAM_1\t100\nPARAM1,100\n")370371# Call the function with the temporary file372update_parameter_documentation(self.doc_dict, self.temp_file.name, "missionplanner")373374# Read the updated content from the temporary file375with open(self.temp_file.name, "r", encoding="utf-8") as file:376updated_content = file.read()377378expected_content = '''# Param _ 1379# Documentation for Param_1380# Field_1: Value_1381# Field_2: Value_2382# Code_1: Value_1383# Code_2: Value_2384PARAM_1\t100385386# Param 1387# Documentation for Param 1388# Field1: Value1389# Field2: Value2390# Code1: Value1391# Code2: Value2392PARAM1,100393394# Param 2395# Documentation for Param 2396# Field3: Value3397# Field4: Value4398# Code3: Value3399# Code4: Value4400PARAM2 100 # ignore, me401'''402self.assertEqual(updated_content, expected_content)403404def test_update_parameter_documentation_sorting_mavproxy(self):405# Write some initial content to the temporary file406with open(self.temp_file.name, "w", encoding="utf-8") as file:407file.write("PARAM2 100\nPARAM_1\t100\nPARAM1,100\n")408409# Call the function with the temporary file410update_parameter_documentation(self.doc_dict, self.temp_file.name, "mavproxy")411412# Read the updated content from the temporary file413with open(self.temp_file.name, "r", encoding="utf-8") as file:414updated_content = file.read()415416expected_content = '''# Param 1417# Documentation for Param 1418# Field1: Value1419# Field2: Value2420# Code1: Value1421# Code2: Value2422PARAM1,100423424# Param 2425# Documentation for Param 2426# Field3: Value3427# Field4: Value4428# Code3: Value3429# Code4: Value4430PARAM2 100431432# Param _ 1433# Documentation for Param_1434# Field_1: Value_1435# Field_2: Value_2436# Code_1: Value_1437# Code_2: Value_2438PARAM_1\t100439'''440self.assertEqual(updated_content, expected_content)441442def test_update_parameter_documentation_invalid_line_format(self):443# Write some initial content to the temporary file with an invalid line format444with open(self.temp_file.name, "w", encoding="utf-8") as file:445file.write("%INVALID_LINE_FORMAT\n")446447# Call the function with the temporary file448with self.assertRaises(SystemExit) as cm:449update_parameter_documentation(self.doc_dict, self.temp_file.name)450451# Check if the SystemExit exception contains the expected message452self.assertEqual(cm.exception.code, "Invalid line in input file")453454@patch('logging.Logger.info')455def test_print_read_only_params(self, mock_info):456# Mock XML data457xml_data = '''458<root>459<param name="PARAM1" humanName="Param 1" documentation="Documentation for Param 1">460<field name="ReadOnly">True</field>461<field name="Field1">Value1</field>462<field name="Field2">Value2</field>463<values>464<value code="Code1">Value1</value>465<value code="Code2">Value2</value>466</values>467</param>468<param name="PARAM2" humanName="Param 2" documentation="Documentation for Param 2">469<field name="Field3">Value3</field>470<field name="Field4">Value4</field>471<values>472<value code="Code3">Value3</value>473<value code="Code4">Value4</value>474</values>475</param>476</root>477'''478root = ET.fromstring(xml_data)479doc_dict = create_doc_dict(root, "VehicleType")480481# Call the function with the mock XML data482print_read_only_params(doc_dict)483484# Check if the parameter name was logged485mock_info.assert_has_calls([mock.call('ReadOnly parameters:'), mock.call('PARAM1')])486487def test_update_parameter_documentation_invalid_target(self):488# Call the function with an invalid target489with self.assertRaises(ValueError):490update_parameter_documentation(self.doc_dict, "invalid_target")491492def test_invalid_parameter_name(self):493# Write some initial content to the temporary file494with open(self.temp_file.name, "w", encoding="utf-8") as file:495file.write("INVALID_$PARAM 100\n")496497# Call the function with the temporary file498with self.assertRaises(SystemExit):499update_parameter_documentation(self.doc_dict, self.temp_file.name)500501def test_update_parameter_documentation_too_long_parameter_name(self):502# Write some initial content to the temporary file503with open(self.temp_file.name, "w", encoding="utf-8") as file:504file.write("TOO_LONG_PARAMETER_NAME 100\n")505506# Call the function with the temporary file507with self.assertRaises(SystemExit):508update_parameter_documentation(self.doc_dict, self.temp_file.name)509510@patch('logging.Logger.warning')511def test_missing_parameter_documentation(self, mock_warning):512# Write some initial content to the temporary file513with open(self.temp_file.name, "w", encoding="utf-8") as file:514file.write("MISSING_DOC_PARA 100\n")515516# Call the function with the temporary file517update_parameter_documentation(self.doc_dict, self.temp_file.name)518519# Check if the warnings were logged520mock_warning.assert_has_calls([521mock.call('Read file %s with %d parameters, but only %s of which got documented', self.temp_file.name, 1, 0),522mock.call('No documentation found for: %s', 'MISSING_DOC_PARA')523])524525def test_empty_parameter_file(self):526# Call the function with the temporary file527update_parameter_documentation(self.doc_dict, self.temp_file.name)528529# Read the updated content from the temporary file530with open(self.temp_file.name, "r", encoding="utf-8") as file:531updated_content = file.read()532533# Check if the file is still empty534self.assertEqual(updated_content, "")535536537if __name__ == '__main__':538unittest.main()539540541