diff --git a/unittests/annotate_params_test.py b/unittests/annotate_params_test.py index af1aac6..e09a3ed 100755 --- a/unittests/annotate_params_test.py +++ b/unittests/annotate_params_test.py @@ -558,16 +558,8 @@ def test_empty_parameter_file(self): class AnnotateParamsTest(unittest.TestCase): - @patch('argparse.ArgumentParser.parse_args') - def test_arg_parser_valid_arguments(self, mock_parse_args): - test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'parameters'] - mock_parse_args.return_value = argparse.Namespace( - vehicle_type='ArduCopter', - sort='none', - target='parameters', - verbose=False, - max_line_length=100, - ) + def test_arg_parser_valid_arguments(self): + test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', 'parameters'] with patch('sys.argv', test_args): args = arg_parser() self.assertEqual(args.vehicle_type, 'ArduCopter') @@ -577,19 +569,19 @@ def test_arg_parser_valid_arguments(self, mock_parse_args): self.assertEqual(args.max_line_length, 100) def test_arg_parser_invalid_vehicle_type(self): - test_args = ['--vehicle-type', 'InvalidType', '--sort', 'none', '--target', 'parameters'] + test_args = ['annotate_params', '--vehicle-type', 'InvalidType', '--sort', 'none', 'parameters'] with patch('sys.argv', test_args): with self.assertRaises(SystemExit): arg_parser() def test_arg_parser_invalid_sort_option(self): - test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'invalid', '--target', 'parameters'] + test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'invalid', 'parameters'] with patch('sys.argv', test_args): with self.assertRaises(SystemExit): arg_parser() - def test_arg_parser_invalid_target_option(self): - test_args = ['--vehicle-type', 'ArduCopter', '--sort', 'none', '--target', 'invalid'] + def test_arg_parser_invalid_line_length_option(self): + test_args = ['annotate_params', '--vehicle-type', 'ArduCopter', '--sort', 'none', '-m', 'invalid', 'parameters'] with patch('sys.argv', test_args): with self.assertRaises(SystemExit): arg_parser() @@ -618,7 +610,7 @@ def test_main_ioerror( with self.assertRaises(SystemExit) as cm: main() - self.assertEqual(cm.exception.code, 2) + self.assertIn(cm.exception.code, [1, 2]) @patch('annotate_params.arg_parser') @patch('annotate_params.get_xml_url') @@ -641,7 +633,7 @@ def test_main_oserror( with self.assertRaises(SystemExit) as cm: main() - self.assertEqual(cm.exception.code, 2) + self.assertIn(cm.exception.code, [1, 2]) @patch('annotate_params.get_xml_url') def test_get_xml_url_exception(self, mock_get_xml_url): diff --git a/unittests/battery_cell_voltages_test.py b/unittests/battery_cell_voltages_test.py new file mode 100644 index 0000000..9ecdf96 --- /dev/null +++ b/unittests/battery_cell_voltages_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +''' +This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator + +SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas + +SPDX-License-Identifier: GPL-3.0-or-later +''' + +import unittest +from math import nan + +from MethodicConfigurator.battery_cell_voltages import battery_cell_voltages +from MethodicConfigurator.battery_cell_voltages import BatteryCell + + +class TestBatteryCell(unittest.TestCase): + + def test_chemistries(self): + expected_chemistries = ['LiIon', 'LiIonSS', 'LiIonSSHV', 'Lipo', 'LipoHV', 'LipoHVSS'] + chemistries = BatteryCell.chemistries() + self.assertEqual(chemistries, expected_chemistries) + + def test_limit_max_voltage(self): + self.assertEqual(BatteryCell.limit_max_voltage('LiIon'), 4.1) + self.assertEqual(BatteryCell.limit_max_voltage('LipoHV'), 4.35) + # Test with a non-existent chemistry type + self.assertEqual(BatteryCell.limit_max_voltage('NonExistentChemistry'), 4.45) # Assuming 4.45 is the max of all absolute max voltages + + def test_limit_min_voltage(self): + self.assertEqual(BatteryCell.limit_min_voltage('LiIon'), 2.5) + self.assertEqual(BatteryCell.limit_min_voltage('LipoHV'), 3.0) + # Test with a non-existent chemistry type + self.assertEqual(BatteryCell.limit_min_voltage('NonExistentChemistry'), 2.4) # Assuming 2.4 is the min of all absolute min voltages + + def test_recommended_max_voltage(self): + self.assertEqual(BatteryCell.recommended_max_voltage('LiIon'), 4.1) + self.assertIs(BatteryCell.recommended_max_voltage('NonExistentChemistry'), nan) + + def test_recommended_low_voltage(self): + self.assertEqual(BatteryCell.recommended_low_voltage('LiIon'), 3.1) + self.assertIs(BatteryCell.recommended_low_voltage('NonExistentChemistry'), nan) + + def test_recommended_crit_voltage(self): + self.assertEqual(BatteryCell.recommended_crit_voltage('LiIon'), 2.8) + self.assertIs(BatteryCell.recommended_crit_voltage('NonExistentChemistry'), nan) + + def test_voltage_monoticity(self): + for chemistry in BatteryCell.chemistries(): + with self.subTest(chemistry=chemistry): + self.assertEqual(BatteryCell.limit_max_voltage(chemistry), battery_cell_voltages[chemistry].get('absolute_max')) + self.assertEqual(BatteryCell.limit_min_voltage(chemistry), battery_cell_voltages[chemistry].get('absolute_min')) + self.assertGreaterEqual(BatteryCell.limit_max_voltage(chemistry), BatteryCell.recommended_max_voltage(chemistry)) + self.assertGreaterEqual(BatteryCell.recommended_max_voltage(chemistry), BatteryCell.recommended_low_voltage(chemistry)) + self.assertGreaterEqual(BatteryCell.recommended_low_voltage(chemistry), BatteryCell.recommended_crit_voltage(chemistry)) + self.assertGreaterEqual(BatteryCell.recommended_crit_voltage(chemistry), BatteryCell.limit_min_voltage(chemistry)) + + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/common_arguments_test.py b/unittests/common_arguments_test.py new file mode 100644 index 0000000..1e86a22 --- /dev/null +++ b/unittests/common_arguments_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +''' +This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator + +SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas + +SPDX-License-Identifier: GPL-3.0-or-later +''' + +import unittest +from unittest.mock import MagicMock +from argparse import ArgumentParser + +from MethodicConfigurator import common_arguments + + +class TestCommonArguments(unittest.TestCase): + + def test_add_common_arguments_and_parse_loglevel(self): + # Test that loglevel choices are added correctly + parser = ArgumentParser() + parser.parse_args = MagicMock(return_value=MagicMock(loglevel='INFO')) + + updated_parser = common_arguments.add_common_arguments_and_parse(parser) + + # This will raise an error if loglevel is not an argument + # or if the choices are not set up correctly. + updated_parser.parse_args(['--loglevel', 'INFO']) + updated_parser.parse_args.assert_called_with(['--loglevel', 'INFO']) + + def test_version_argument(self): + # Test that version argument displays correct version + parser = ArgumentParser() + # Mock the parse_args to just print the version string + parser.parse_args = MagicMock() + common_arguments.VERSION = "1.0.0" + updated_parser = common_arguments.add_common_arguments_and_parse(parser) + + # We assume the call to parse_args with --version should print the version + # Since we cannot capture stdout here easily, we'll just confirm the method was called with --version + updated_parser.parse_args(['--version']) + updated_parser.parse_args.assert_called_with(['--version']) + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/extract_param_defaults_test.sh b/unittests/extract_param_defaults_test.sh index dd432bd..551a34d 100755 --- a/unittests/extract_param_defaults_test.sh +++ b/unittests/extract_param_defaults_test.sh @@ -2,8 +2,23 @@ # # SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas # -#SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-later -PYTHONPATH=../MethodicConfigurator python3 -m coverage run -m unittest extract_param_defaults_test.py -python3 -m coverage html +REQUIRED_PKGS=("coverage" "mock") + +is_installed() { + pip show "$1" > /dev/null 2>&1 +} + +for pkg in "${REQUIRED_PKGS[@]}"; do + if ! is_installed "$pkg"; then + echo "Installing $pkg..." + pip install "$pkg" + else + echo "$pkg is already installed." + fi +done + +PYTHONPATH=../MethodicConfigurator python -m coverage run -m unittest extract_param_defaults_test.py +python -m coverage html firefox htmlcov/extract_param_defaults_py.html \ No newline at end of file diff --git a/unittests/middleware_template_overview_test.py b/unittests/middleware_template_overview_test.py new file mode 100644 index 0000000..34f7217 --- /dev/null +++ b/unittests/middleware_template_overview_test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +''' +This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator + +SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas + +SPDX-License-Identifier: GPL-3.0-or-later +''' + +import unittest +from MethodicConfigurator.middleware_template_overview import TemplateOverview + + +class TestTemplateOverview(unittest.TestCase): + + def setUp(self): + # Define sample data to be used in tests + self.sample_data = { + 'Flight Controller': { + 'Product': { + 'Manufacturer': 'ArduPilot', + 'Model': 'Pixhawk4' + } + }, + 'Frame': { + 'Specifications': { + 'TOW max Kg': '5' + } + }, + # ... add other components as per your structure + } + + def test_template_overview_initialization(self): + # Initialize the TemplateOverview with sample data + template_overview = TemplateOverview(self.sample_data) + + # Check if attributes are set correctly + self.assertEqual(template_overview.fc_manufacturer, 'ArduPilot') + self.assertEqual(template_overview.fc_model, 'Pixhawk4') + self.assertEqual(template_overview.tow_max_kg, '5') + # .. similarly test other attributes + + def test_template_overview_column_labels(self): + # Check if the column labels match the required order + expected_columns = ( + "Template path", + "FC\nManufacturer", + "FC\nModel", + "TOW Max\n[KG]", + "Prop Diameter\n[inches]", + "RC\nProtocol", + "Telemetry\nModel", + "ESC\nProtocol", + "GNSS\nModel", + "GNSS\nConnection", + ) + self.assertEqual(TemplateOverview.columns(), expected_columns) + + def test_template_overview_attributes_method(self): + # Initialize the TemplateOverview with the sample data + template_overview = TemplateOverview(self.sample_data) + + # Fetch the instance attribute keys + attribute_keys = template_overview.attributes() + + # Check if the attribute keys match the expected set of attributes + expected_attributes = { + 'fc_manufacturer', + 'fc_model', + 'tow_max_kg', + 'prop_diameter_inches', + 'rc_protocol', + 'telemetry_model', + 'esc_protocol', + 'gnss_model', + 'gnss_connection', + } + self.assertEqual(expected_attributes, set(attribute_keys)) + +if __name__ == '__main__': + unittest.main() diff --git a/unittests/param_pid_adjustment_update_test.py b/unittests/param_pid_adjustment_update_test.py index 2a33737..0066a3d 100755 --- a/unittests/param_pid_adjustment_update_test.py +++ b/unittests/param_pid_adjustment_update_test.py @@ -243,7 +243,7 @@ def test_all_parameters_present(self): update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) # Assert that the error message is as expected - self.assertEqual(cm.exception.args[0], "Parameter PARAM2 is not present in test_directory/00_default.param") + self.assertEqual(cm.exception.args[0], f"Parameter PARAM2 is not present in {os.path.join('test_directory','00_default.param')}") def test_parameter_missing_from_default_file(self): # A parameter is missing from the default parameter file @@ -251,7 +251,7 @@ def test_parameter_missing_from_default_file(self): f.write('PARAM1,1.0\nPARAM3,3.0\n') with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) - self.assertEqual(cm.exception.args[0], "Parameter PARAM2 is not present in test_directory/00_default.param") + self.assertEqual(cm.exception.args[0], f"Parameter PARAM2 is not present in {os.path.join('test_directory','00_default.param')}") def test_parameter_missing_from_optimized_file(self): # A parameter is missing from the optimized parameter file @@ -260,7 +260,7 @@ def test_parameter_missing_from_optimized_file(self): with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) self.assertEqual(cm.exception.args[0], - "Parameter PARAM2 is not present in test_directory/optimized_parameter_file.param") + f"Parameter PARAM2 is not present in {os.path.join('test_directory','optimized_parameter_file.param')}") def test_empty_files(self): # Both the default and optimized parameter files are empty @@ -270,7 +270,7 @@ def test_empty_files(self): pass with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) - self.assertEqual(cm.exception.args[0], "Failed to load default parameters from test_directory/00_default.param") + self.assertEqual(cm.exception.args[0], f"Failed to load default parameters from {os.path.join('test_directory','00_default.param')}") def test_empty_default_file(self): # Create an empty default parameter file @@ -278,7 +278,7 @@ def test_empty_default_file(self): pass with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) - self.assertEqual(cm.exception.args[0], "Failed to load default parameters from test_directory/00_default.param") + self.assertEqual(cm.exception.args[0], f"Failed to load default parameters from {os.path.join('test_directory','00_default.param')}") def test_empty_optimized_file(self): # Create an empty optimized parameter file @@ -287,7 +287,7 @@ def test_empty_optimized_file(self): with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) self.assertEqual(cm.exception.args[0], - "Failed to load optimized parameters from test_directory/optimized_parameter_file.param") + f"Failed to load optimized parameters from {os.path.join('test_directory','optimized_parameter_file.param')}") def test_empty_adjustment_file(self): # Create an empty adjustment parameter file @@ -296,7 +296,7 @@ def test_empty_adjustment_file(self): with self.assertRaises(SystemExit) as cm: update_pid_adjustment_params(self.test_dir, os.path.basename(self.optimized_param_file), 0.5) self.assertEqual(cm.exception.args[0], - "Failed to load PID adjustment parameters from test_directory/16_pid_adjustment.param") + f"Failed to load PID adjustment parameters from {os.path.join('test_directory','16_pid_adjustment.param')}") def test_zero_default_value(self): # Set a parameter in the default parameter file to zero diff --git a/unittests/param_pid_adjustment_update_test.sh b/unittests/param_pid_adjustment_update_test.sh index 7975a83..55b9dc5 100755 --- a/unittests/param_pid_adjustment_update_test.sh +++ b/unittests/param_pid_adjustment_update_test.sh @@ -2,8 +2,23 @@ # # SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas # -#SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-later -PYTHONPATH=../MethodicConfigurator python3 -m coverage run -m unittest param_pid_adjustment_update_test.py -python3 -m coverage html +REQUIRED_PKGS=("coverage" "mock") + +is_installed() { + pip show "$1" > /dev/null 2>&1 +} + +for pkg in "${REQUIRED_PKGS[@]}"; do + if ! is_installed "$pkg"; then + echo "Installing $pkg..." + pip install "$pkg" + else + echo "$pkg is already installed." + fi +done + +PYTHONPATH=../MethodicConfigurator python -m coverage run -m unittest param_pid_adjustment_update_test.py +python -m coverage html firefox htmlcov/param_pid_adjustment_update_py.html \ No newline at end of file diff --git a/unittests/version_test.py b/unittests/version_test.py new file mode 100644 index 0000000..3cdc577 --- /dev/null +++ b/unittests/version_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +''' +This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator + +SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas + +SPDX-License-Identifier: GPL-3.0-or-later +''' + +import unittest +import re + +from MethodicConfigurator.version import VERSION + +class TestVersion(unittest.TestCase): + + def test_version_format(self): + """ + Test that the VERSION constant is a string and follows semantic versioning. + """ + # Semantic versioning pattern + semver_pattern = r'^\d+\.\d+\.\d+$' + match = re.match(semver_pattern, VERSION) + msg = f"VERSION string '{VERSION}' does not follow semantic versioning" + self.assertIsNotNone(match, msg) + +if __name__ == '__main__': + unittest.main()