diff options
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/test_config_diff.py | 11 | ||||
-rw-r--r-- | src/tests/test_config_parser.py | 4 | ||||
-rw-r--r-- | src/tests/test_configd_inspect.py | 210 | ||||
-rw-r--r-- | src/tests/test_initial_setup.py | 4 | ||||
-rw-r--r-- | src/tests/test_template.py | 9 | ||||
-rw-r--r-- | src/tests/test_utils_network.py | 11 |
6 files changed, 166 insertions, 83 deletions
diff --git a/src/tests/test_config_diff.py b/src/tests/test_config_diff.py index 39e17613a..4017fff4d 100644 --- a/src/tests/test_config_diff.py +++ b/src/tests/test_config_diff.py @@ -31,11 +31,11 @@ class TestConfigDiff(TestCase): def test_unit(self): diff = vyos.configtree.DiffTree(self.config_left, self.config_null) sub = diff.sub - self.assertEqual(sub.to_string(), self.config_left.to_string()) + self.assertEqual(sub, self.config_left) diff = vyos.configtree.DiffTree(self.config_null, self.config_left) add = diff.add - self.assertEqual(add.to_string(), self.config_left.to_string()) + self.assertEqual(add, self.config_left) def test_symmetry(self): lr_diff = vyos.configtree.DiffTree(self.config_left, @@ -45,10 +45,10 @@ class TestConfigDiff(TestCase): sub = lr_diff.sub add = rl_diff.add - self.assertEqual(sub.to_string(), add.to_string()) + self.assertEqual(sub, add) add = lr_diff.add sub = rl_diff.sub - self.assertEqual(add.to_string(), sub.to_string()) + self.assertEqual(add, sub) def test_identity(self): lr_diff = vyos.configtree.DiffTree(self.config_left, @@ -61,6 +61,9 @@ class TestConfigDiff(TestCase): r_union = vyos.configtree.union(add, inter) l_union = vyos.configtree.union(sub, inter) + # here we must compare string representations instead of using + # dunder equal, as we assert equivalence of the values list, which + # is optionally ordered at render self.assertEqual(r_union.to_string(), self.config_right.to_string(ordered_values=True)) self.assertEqual(l_union.to_string(), diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py index 9a4f02859..1b4a57311 100644 --- a/src/tests/test_config_parser.py +++ b/src/tests/test_config_parser.py @@ -51,3 +51,7 @@ class TestConfigParser(TestCase): def test_rename_duplicate(self): with self.assertRaises(vyos.configtree.ConfigTreeError): self.config.rename(["top-level-tag-node", "foo"], "bar") + + def test_leading_slashes(self): + self.assertTrue(self.config.exists(["normal-node", "value-with-leading-slashes"])) + self.assertEqual(self.config.return_value(["normal-node", "value-with-leading-slashes"]), "//other-value") diff --git a/src/tests/test_configd_inspect.py b/src/tests/test_configd_inspect.py index ccd631893..a0470221d 100644 --- a/src/tests/test_configd_inspect.py +++ b/src/tests/test_configd_inspect.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright (C) 2020-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -12,93 +12,151 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os -import re +import ast import json -import warnings -import importlib.util -from inspect import signature -from inspect import getsource -from functools import wraps from unittest import TestCase INC_FILE = 'data/configd-include.json' CONF_DIR = 'src/conf_mode' -f_list = ['get_config', 'verify', 'generate', 'apply'] - -def import_script(s): - path = os.path.join(CONF_DIR, s) - name = os.path.splitext(s)[0].replace('-', '_') - spec = importlib.util.spec_from_file_location(name, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - -# importing conf_mode scripts imports jinja2 with deprecation warning -def ignore_deprecation_warning(f): - @wraps(f) - def decorated_function(*args, **kwargs): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - f(*args, **kwargs) - return decorated_function +funcs = ['get_config', 'verify', 'generate', 'apply'] + + +class FunctionSig(ast.NodeVisitor): + def __init__(self): + self.func_sig_len = dict.fromkeys(funcs, None) + self.get_config_default_values = [] + + def visit_FunctionDef(self, node): + func_name = node.name + if func_name in funcs: + self.func_sig_len[func_name] = len(node.args.args) + + if func_name == 'get_config': + for default in node.args.defaults: + if isinstance(default, ast.Constant): + self.get_config_default_values.append(default.value) + + self.generic_visit(node) + + def get_sig_lengths(self): + return self.func_sig_len + + def get_config_default(self): + return self.get_config_default_values[0] + + +class LegacyCall(ast.NodeVisitor): + def __init__(self): + self.legacy_func_count = 0 + + def visit_Constant(self, node): + value = node.value + if isinstance(value, str): + if 'my_set' in value or 'my_delete' in value: + self.legacy_func_count += 1 + + self.generic_visit(node) + + def get_legacy_func_count(self): + return self.legacy_func_count + + +class ConfigInstance(ast.NodeVisitor): + def __init__(self): + self.count = 0 + + def visit_Call(self, node): + if isinstance(node.func, ast.Name): + name = node.func.id + if name == 'Config': + self.count += 1 + self.generic_visit(node) + + def get_count(self): + return self.count + + +class FunctionConfigInstance(ast.NodeVisitor): + def __init__(self): + self.func_config_instance = dict.fromkeys(funcs, 0) + + def visit_FunctionDef(self, node): + func_name = node.name + if func_name in funcs: + config_instance = ConfigInstance() + config_instance.visit(node) + self.func_config_instance[func_name] = config_instance.get_count() + self.generic_visit(node) + + def get_func_config_instance(self): + return self.func_config_instance + class TestConfigdInspect(TestCase): def setUp(self): + self.ast_list = [] + with open(INC_FILE) as f: self.inc_list = json.load(f) - @ignore_deprecation_warning - def test_signatures(self): for s in self.inc_list: - m = import_script(s) - for i in f_list: - f = getattr(m, i, None) - self.assertIsNotNone(f, f"'{s}': missing function '{i}'") - sig = signature(f) - par = sig.parameters - l = len(par) - self.assertEqual(l, 1, - f"'{s}': '{i}' incorrect signature") - if i == 'get_config': - for p in par.values(): - self.assertTrue(p.default is None, - f"'{s}': '{i}' incorrect signature") - - @ignore_deprecation_warning - def test_function_instance(self): - for s in self.inc_list: - m = import_script(s) - for i in f_list: - f = getattr(m, i, None) - if not f: - continue - str_f = getsource(f) - # Regex not XXXConfig() T3108 - n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_f)) - if i == 'get_config': - self.assertEqual(n, 1, - f"'{s}': '{i}' no instance of Config") - if i != 'get_config': - self.assertEqual(n, 0, - f"'{s}': '{i}' instance of Config") - - @ignore_deprecation_warning - def test_file_instance(self): - for s in self.inc_list: - m = import_script(s) - str_m = getsource(m) - # Regex not XXXConfig T3108 - n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_m)) - self.assertEqual(n, 1, - f"'{s}' more than one instance of Config") - - @ignore_deprecation_warning + s_path = f'{CONF_DIR}/{s}' + with open(s_path) as f: + s_str = f.read() + s_tree = ast.parse(s_str) + self.ast_list.append((s, s_tree)) + + def test_signatures(self): + for s, t in self.ast_list: + visitor = FunctionSig() + visitor.visit(t) + sig_lens = visitor.get_sig_lengths() + + for f in funcs: + self.assertIsNotNone(sig_lens[f], f"'{s}': '{f}' missing") + self.assertEqual(sig_lens[f], 1, f"'{s}': '{f}' incorrect signature") + + self.assertEqual( + visitor.get_config_default(), + None, + f"'{s}': 'get_config' incorrect signature", + ) + + def test_file_config_instance(self): + for s, t in self.ast_list: + visitor = ConfigInstance() + visitor.visit(t) + count = visitor.get_count() + + self.assertEqual(count, 1, f"'{s}' more than one instance of Config") + + def test_function_config_instance(self): + for s, t in self.ast_list: + visitor = FunctionConfigInstance() + visitor.visit(t) + func_config_instance = visitor.get_func_config_instance() + + for f in funcs: + if f == 'get_config': + self.assertTrue( + func_config_instance[f] > 0, + f"'{s}': '{f}' no instance of Config", + ) + self.assertTrue( + func_config_instance[f] < 2, + f"'{s}': '{f}' more than one instance of Config", + ) + else: + self.assertEqual( + func_config_instance[f], 0, f"'{s}': '{f}' instance of Config" + ) + def test_config_modification(self): - for s in self.inc_list: - m = import_script(s) - str_m = getsource(m) - n = str_m.count('my_set') - self.assertEqual(n, 0, f"'{s}' modifies config") + for s, t in self.ast_list: + visitor = LegacyCall() + visitor.visit(t) + legacy_func_count = visitor.get_legacy_func_count() + + self.assertEqual(legacy_func_count, 0, f"'{s}' modifies config") diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index 4cd5fb169..7737f9df5 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -92,8 +92,8 @@ class TestInitialSetup(TestCase): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute'])) if __name__ == "__main__": unittest.main() diff --git a/src/tests/test_template.py b/src/tests/test_template.py index 6377f6da5..7cae867a0 100644 --- a/src/tests/test_template.py +++ b/src/tests/test_template.py @@ -190,3 +190,12 @@ class TestVyOSTemplate(TestCase): for group_name, group_config in data['ike_group'].items(): ciphers = vyos.template.get_esp_ike_cipher(group_config) self.assertIn(IKEv2_DEFAULT, ','.join(ciphers)) + + def test_get_default_port(self): + from vyos.defaults import internal_ports + + with self.assertRaises(RuntimeError): + vyos.template.get_default_port('UNKNOWN') + + self.assertEqual(vyos.template.get_default_port('certbot_haproxy'), + internal_ports['certbot_haproxy']) diff --git a/src/tests/test_utils_network.py b/src/tests/test_utils_network.py index d68dec16f..92fde447d 100644 --- a/src/tests/test_utils_network.py +++ b/src/tests/test_utils_network.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright (C) 2020-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -43,3 +43,12 @@ class TestVyOSUtilsNetwork(TestCase): self.assertFalse(vyos.utils.network.is_loopback_addr('::2')) self.assertFalse(vyos.utils.network.is_loopback_addr('192.0.2.1')) + + def test_check_port_availability(self): + self.assertTrue(vyos.utils.network.check_port_availability('::1', 8080)) + self.assertTrue(vyos.utils.network.check_port_availability('127.0.0.1', 8080)) + self.assertTrue(vyos.utils.network.check_port_availability(None, 8080, protocol='udp')) + # We do not have 192.0.2.1 configured on this system + self.assertFalse(vyos.utils.network.check_port_availability('192.0.2.1', 443)) + # We do not have 2001:db8::1 configured on this system + self.assertFalse(vyos.utils.network.check_port_availability('2001:db8::1', 80, protocol='udp')) |