nuts 3.2.0__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nuts-3.2.0 → nuts-3.3.0}/PKG-INFO +8 -3
- {nuts-3.2.0 → nuts-3.3.0}/README.md +4 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_bgp_neighbors.py +20 -7
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_get_arp.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_get_config.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_get_users.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_get_vlans.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_interfaces.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_lldp_neighbors.py +4 -3
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_network_instances.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/napalm_ping.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/netmiko_cdp_neighbors.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/netmiko_ospf_neighbors.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/context.py +2 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/context.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/filters.py +1 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/result.py +1 -1
- nuts-3.3.0/nuts/hooks.py +9 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/index.py +2 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/plugin.py +18 -3
- {nuts-3.2.0 → nuts-3.3.0}/nuts/yamlloader.py +3 -0
- {nuts-3.2.0 → nuts-3.3.0}/pyproject.toml +18 -14
- {nuts-3.2.0 → nuts-3.3.0}/LICENSE +0 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/__init__.py +0 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/__init__.py +0 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/base_tests/netmiko_iperf.py +1 -1
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/__init__.py +0 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/converters.py +0 -0
- {nuts-3.2.0 → nuts-3.3.0}/nuts/helpers/errors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nuts
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Network Unit Testing System
|
|
5
5
|
Home-page: https://github.com/network-unit-testing-system/nuts
|
|
6
6
|
License: MIT
|
|
@@ -14,13 +14,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
18
|
Classifier: Topic :: System :: Networking
|
|
18
19
|
Requires-Dist: PyYAML (>=6.0,<7.0)
|
|
19
20
|
Requires-Dist: nornir (>=3.3.0,<4.0.0)
|
|
20
|
-
Requires-Dist: nornir-napalm (>=0.
|
|
21
|
+
Requires-Dist: nornir-napalm (>=0.5.0,<0.6.0)
|
|
21
22
|
Requires-Dist: nornir-netmiko (>=1.0.0,<2.0.0)
|
|
22
23
|
Requires-Dist: nornir-utils (>=0.2.0,<0.3.0)
|
|
23
|
-
Requires-Dist: pytest (>=7
|
|
24
|
+
Requires-Dist: pytest (>=7,<8)
|
|
24
25
|
Project-URL: Repository, https://github.com/network-unit-testing-system/nuts
|
|
25
26
|
Description-Content-Type: text/markdown
|
|
26
27
|
|
|
@@ -76,6 +77,7 @@ Each test bundle contains the following structure:
|
|
|
76
77
|
test_class: <name of the test class>
|
|
77
78
|
label: <label to uniquely identify the test> # optional
|
|
78
79
|
test_execution: <additional data used to execute the test> # optional
|
|
80
|
+
test_extras: <additional data can be provided to the context for custom usage> # optional
|
|
79
81
|
test_data: <data used to generate the test cases>
|
|
80
82
|
...
|
|
81
83
|
```
|
|
@@ -95,6 +97,9 @@ Therefore the key-value pairs must be consistent with the key-value pairs of the
|
|
|
95
97
|
As an example, the test definition `napalm_ping.py` calls a nornir task to execute napalm's ping-command.
|
|
96
98
|
This allows the additional `max_drop` parameter in `test_execution`, since it is in turn pre-defined by napalm.
|
|
97
99
|
|
|
100
|
+
`test_extras`: Additional data that can be accessed through the `nuts_parameters` property.
|
|
101
|
+
These data are not internally utilized and can be passed for use in custom code.
|
|
102
|
+
|
|
98
103
|
`test_data`: Data that is used to parametrize the tests in the test class which have the `pytest.mark.nuts` annotation. It is additionally part of the `nuts_parameters` property.
|
|
99
104
|
|
|
100
105
|
### Example: CDP Neighbors
|
|
@@ -50,6 +50,7 @@ Each test bundle contains the following structure:
|
|
|
50
50
|
test_class: <name of the test class>
|
|
51
51
|
label: <label to uniquely identify the test> # optional
|
|
52
52
|
test_execution: <additional data used to execute the test> # optional
|
|
53
|
+
test_extras: <additional data can be provided to the context for custom usage> # optional
|
|
53
54
|
test_data: <data used to generate the test cases>
|
|
54
55
|
...
|
|
55
56
|
```
|
|
@@ -69,6 +70,9 @@ Therefore the key-value pairs must be consistent with the key-value pairs of the
|
|
|
69
70
|
As an example, the test definition `napalm_ping.py` calls a nornir task to execute napalm's ping-command.
|
|
70
71
|
This allows the additional `max_drop` parameter in `test_execution`, since it is in turn pre-defined by napalm.
|
|
71
72
|
|
|
73
|
+
`test_extras`: Additional data that can be accessed through the `nuts_parameters` property.
|
|
74
|
+
These data are not internally utilized and can be passed for use in custom code.
|
|
75
|
+
|
|
72
76
|
`test_data`: Data that is used to parametrize the tests in the test class which have the `pytest.mark.nuts` annotation. It is additionally part of the `nuts_parameters` property.
|
|
73
77
|
|
|
74
78
|
### Example: CDP Neighbors
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Query BGP neighbors of a device or count them."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Callable, List, Any, Tuple
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
from nornir.core.task import MultiResult, Result
|
|
@@ -12,15 +13,23 @@ from nuts.helpers.result import AbstractHostResultExtractor, NutsResult
|
|
|
12
13
|
class BgpNeighborsExtractor(AbstractHostResultExtractor):
|
|
13
14
|
def single_transform(
|
|
14
15
|
self, single_result: MultiResult
|
|
15
|
-
) -> Dict[str, Dict[str, NutsResult]]:
|
|
16
|
+
) -> Dict[Tuple[str, str], Dict[str, NutsResult]]:
|
|
17
|
+
test_exectuion = self._nuts_ctx.nuts_parameters.get("test_execution")
|
|
18
|
+
if test_exectuion:
|
|
19
|
+
vrf = test_exectuion.get("vrf", "global")
|
|
20
|
+
if not vrf:
|
|
21
|
+
vrf = "global"
|
|
22
|
+
else:
|
|
23
|
+
vrf = "global"
|
|
24
|
+
|
|
16
25
|
neighbors = self._simple_extract(single_result)["bgp_neighbors"]
|
|
17
|
-
if
|
|
26
|
+
if vrf not in neighbors:
|
|
18
27
|
return {}
|
|
19
|
-
|
|
20
|
-
router_id =
|
|
28
|
+
scope = neighbors[vrf]
|
|
29
|
+
router_id = scope["router_id"]
|
|
21
30
|
return {
|
|
22
31
|
peer: self._add_local_id(details, router_id)
|
|
23
|
-
for peer, details in
|
|
32
|
+
for peer, details in scope["peers"].items()
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
def _add_local_id(self, element: Dict[str, Any], router_id: str) -> Dict[str, Any]:
|
|
@@ -33,6 +42,8 @@ class BgpNeighborsContext(NornirNutsContext):
|
|
|
33
42
|
return napalm_get
|
|
34
43
|
|
|
35
44
|
def nuts_arguments(self) -> Dict[str, List[str]]:
|
|
45
|
+
# Overrides nuts_arguments.nuts_arguments
|
|
46
|
+
# "test_execution" is not passed to the nuts_task
|
|
36
47
|
return {"getters": ["bgp_neighbors"]}
|
|
37
48
|
|
|
38
49
|
def nuts_extractor(self) -> BgpNeighborsExtractor:
|
|
@@ -48,7 +59,9 @@ class TestNapalmBgpNeighborsCount:
|
|
|
48
59
|
self, single_result: NutsResult, neighbor_count: int
|
|
49
60
|
) -> None:
|
|
50
61
|
assert single_result.result is not None
|
|
51
|
-
assert
|
|
62
|
+
assert (
|
|
63
|
+
len(single_result.result) == neighbor_count
|
|
64
|
+
), f"Expected {neighbor_count}; got {len(single_result.result)}"
|
|
52
65
|
|
|
53
66
|
|
|
54
67
|
class TestNapalmBgpNeighbors:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Query LLDP neighbors of a device."""
|
|
2
|
+
|
|
2
3
|
from typing import Dict, Callable, List, Any
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
@@ -28,9 +29,9 @@ class LldpNeighborsExtractor(AbstractHostResultExtractor):
|
|
|
28
29
|
return element
|
|
29
30
|
|
|
30
31
|
def _add_expanded_remote_port(self, element: Dict[str, Any]) -> Dict[str, Any]:
|
|
31
|
-
element[
|
|
32
|
-
"
|
|
33
|
-
|
|
32
|
+
element["remote_port_expanded"] = (
|
|
33
|
+
InterfaceNameConverter().expand_interface_name(element["remote_port"])
|
|
34
|
+
)
|
|
34
35
|
return element
|
|
35
36
|
|
|
36
37
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Provide necessary information that is needed for a specific test."""
|
|
2
|
+
|
|
2
3
|
import pathlib
|
|
3
4
|
from typing import Any, Callable, Optional, Dict, List
|
|
4
5
|
from pytest import Config
|
|
@@ -202,6 +203,7 @@ class NornirNutsContext(NutsContext):
|
|
|
202
203
|
)
|
|
203
204
|
|
|
204
205
|
self.teardown()
|
|
206
|
+
selected_hosts.close_connections(on_good=True, on_failed=True)
|
|
205
207
|
return overall_results
|
|
206
208
|
|
|
207
209
|
def setup(self) -> None:
|
nuts-3.3.0/nuts/hooks.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pytest import FixtureRequest
|
|
2
|
+
from nuts.context import NutsContext
|
|
3
|
+
from nuts.helpers.result import NutsResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def pytest_nuts_single_result(
|
|
7
|
+
request: FixtureRequest, nuts_ctx: NutsContext, result: NutsResult
|
|
8
|
+
) -> None:
|
|
9
|
+
"""Called in the single result fixture"""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Allows to indicate only the test class name in a test bundle."""
|
|
2
|
+
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
5
|
default_index = {
|
|
@@ -10,6 +11,7 @@ default_index = {
|
|
|
10
11
|
"TestNapalmNetworkInstances": "nuts.base_tests.napalm_network_instances",
|
|
11
12
|
"TestNapalmPing": "nuts.base_tests.napalm_ping",
|
|
12
13
|
"TestNapalmUsers": "nuts.base_tests.napalm_get_users",
|
|
14
|
+
"TestNapalmOnlyDefinedUsersExist": "nuts.base_tests.napalm_get_users",
|
|
13
15
|
"TestNapalmConfig": "nuts.base_tests.napalm_get_config",
|
|
14
16
|
"TestNapalmVlans": "nuts.base_tests.napalm_get_vlans",
|
|
15
17
|
"TestNapalmOnlyDefinedVlansExist": "nuts.base_tests.napalm_get_vlans",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Fixtures"""
|
|
2
|
+
|
|
2
3
|
from typing import Optional, Dict, Any
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
@@ -16,6 +17,12 @@ from nuts.helpers.result import NutsResult
|
|
|
16
17
|
from nuts.yamlloader import NutsYamlFile, get_parametrize_data
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def pytest_addhooks(pluginmanager):
|
|
21
|
+
from nuts import hooks
|
|
22
|
+
|
|
23
|
+
pluginmanager.add_hookspecs(hooks)
|
|
24
|
+
|
|
25
|
+
|
|
19
26
|
@pytest.fixture(scope="class")
|
|
20
27
|
def nuts_ctx(request: FixtureRequest) -> NutsContext:
|
|
21
28
|
params = request.node.params
|
|
@@ -26,7 +33,9 @@ def nuts_ctx(request: FixtureRequest) -> NutsContext:
|
|
|
26
33
|
|
|
27
34
|
|
|
28
35
|
@pytest.fixture
|
|
29
|
-
def single_result(
|
|
36
|
+
def single_result(
|
|
37
|
+
nuts_ctx: NutsContext, nuts_test_entry: Dict[str, Any], request: FixtureRequest
|
|
38
|
+
) -> NutsResult:
|
|
30
39
|
"""
|
|
31
40
|
Returns the result which belongs to a specific host
|
|
32
41
|
out of the overall set of results that has been returned by nornir's task.
|
|
@@ -39,6 +48,12 @@ def single_result(nuts_ctx: NutsContext, nuts_test_entry: Dict[str, Any]) -> Nut
|
|
|
39
48
|
"""
|
|
40
49
|
res = nuts_ctx.extractor.single_result(nuts_test_entry)
|
|
41
50
|
res.validate()
|
|
51
|
+
|
|
52
|
+
# Invoke the pytest_nuts_single_result hook to extend result reports.
|
|
53
|
+
request.config.hook.pytest_nuts_single_result(
|
|
54
|
+
request=request, nuts_ctx=nuts_ctx, result=res
|
|
55
|
+
)
|
|
56
|
+
|
|
42
57
|
return res
|
|
43
58
|
|
|
44
59
|
|
|
@@ -64,12 +79,12 @@ def pytest_collect_file(parent: Session, file_path: Path) -> Optional[Collector]
|
|
|
64
79
|
"""
|
|
65
80
|
Performs the collection phase for the given pytest session.
|
|
66
81
|
Collects all test bundles if available, i.e. files starting
|
|
67
|
-
with 'test' and ending in .yaml.
|
|
82
|
+
with 'test' and ending in .yaml or .yml.
|
|
68
83
|
:param parent: pytest session object
|
|
69
84
|
:param file_path: path to test file(s)
|
|
70
85
|
:return: The pytest collector if found
|
|
71
86
|
"""
|
|
72
|
-
if file_path.suffix
|
|
87
|
+
if file_path.suffix in [".yaml", ".yml"] and file_path.name.startswith("test"):
|
|
73
88
|
return NutsYamlFile.from_parent(parent, path=file_path)
|
|
74
89
|
return None
|
|
75
90
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Converts a test bundle (YAML file) into a test class for pytest.
|
|
2
2
|
Based on https://docs.pytest.org/en/stable/example/nonpython.html#yaml-plugin
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import importlib
|
|
5
6
|
from nuts.helpers.context import load_context
|
|
6
7
|
import types
|
|
@@ -106,12 +107,14 @@ class NutsTestFile(pytest.Module):
|
|
|
106
107
|
|
|
107
108
|
test_data = self.test_entry.get("test_data", [])
|
|
108
109
|
test_execution = self.test_entry.get("test_execution")
|
|
110
|
+
test_extras = self.test_entry.get("test_extras")
|
|
109
111
|
yield NutsTestClass.from_parent(
|
|
110
112
|
self,
|
|
111
113
|
name=name,
|
|
112
114
|
class_name=class_name,
|
|
113
115
|
test_data=test_data,
|
|
114
116
|
test_execution=test_execution,
|
|
117
|
+
test_extras=test_extras,
|
|
115
118
|
)
|
|
116
119
|
|
|
117
120
|
|
|
@@ -4,10 +4,17 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "nuts"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.3.0"
|
|
8
8
|
description = "Network Unit Testing System"
|
|
9
|
-
authors = [
|
|
10
|
-
|
|
9
|
+
authors = [
|
|
10
|
+
"Lukas Murer, Méline Sieber, Urs Baumann, Matthias Gabriel, Florian Bruhin",
|
|
11
|
+
"Marco Martinez",
|
|
12
|
+
"Severin Grimm",
|
|
13
|
+
]
|
|
14
|
+
maintainers = [
|
|
15
|
+
"Marco Martinez <marco.maritnez@ost.ch>",
|
|
16
|
+
"Urs Baumann <github@m.ubaumann.ch>",
|
|
17
|
+
]
|
|
11
18
|
classifiers = ["Framework :: Pytest", "Topic :: System :: Networking"]
|
|
12
19
|
homepage = "https://github.com/network-unit-testing-system/nuts"
|
|
13
20
|
repository = "https://github.com/network-unit-testing-system/nuts"
|
|
@@ -16,26 +23,26 @@ readme = "README.md"
|
|
|
16
23
|
|
|
17
24
|
[tool.poetry.dependencies]
|
|
18
25
|
python = "^3.8.1"
|
|
19
|
-
pytest = "^7
|
|
26
|
+
pytest = "^7"
|
|
20
27
|
PyYAML = "^6.0"
|
|
21
28
|
nornir = "^3.3.0"
|
|
22
|
-
nornir-napalm = "^0.
|
|
29
|
+
nornir-napalm = "^0.5.0"
|
|
23
30
|
nornir-netmiko = "^1.0.0"
|
|
24
31
|
nornir-utils = "^0.2.0"
|
|
25
32
|
|
|
26
33
|
[tool.poetry.dev-dependencies]
|
|
27
34
|
tox = "^4.4.11"
|
|
28
35
|
pytest-cov = "^4.0.0"
|
|
29
|
-
black = "^
|
|
30
|
-
sphinx = "^7
|
|
36
|
+
black = "^24.2.0"
|
|
37
|
+
sphinx = "^7"
|
|
31
38
|
mypy = "^1.0.1"
|
|
32
|
-
flake8 = "^
|
|
39
|
+
flake8 = "^7.0.0"
|
|
33
40
|
types-PyYAML = "*"
|
|
34
41
|
types-setuptools = "*"
|
|
35
42
|
types-toml = "*"
|
|
36
43
|
|
|
37
44
|
[tool.poetry.plugins."pytest11"]
|
|
38
|
-
"nuts"="nuts.plugin"
|
|
45
|
+
"nuts" = "nuts.plugin"
|
|
39
46
|
# why "pytest11": https://github.com/pytest-dev/pytest/commit/ed03eef81b220199a819632a27f9c452b8e1fb81
|
|
40
47
|
# https://docs.pytest.org/en/latest/how-to/writing_plugins.html#making-your-plugin-installable-by-others
|
|
41
48
|
|
|
@@ -50,12 +57,10 @@ filterwarnings = [
|
|
|
50
57
|
"ignore:Using or importing the ABCs from 'collections':DeprecationWarning:jnpr\\.junos\\.factcache",
|
|
51
58
|
"ignore:NutsYamlFile\\.fspath is deprecated and will be replaced by NutsYamlFile\\.path\\.",
|
|
52
59
|
]
|
|
53
|
-
markers = [
|
|
54
|
-
"nuts_test_ctx: A NutsContext to be used in tests."
|
|
55
|
-
]
|
|
60
|
+
markers = ["nuts_test_ctx: A NutsContext to be used in tests."]
|
|
56
61
|
|
|
57
62
|
[tool.mypy]
|
|
58
|
-
python_version = 3.8
|
|
63
|
+
python_version = "3.8"
|
|
59
64
|
|
|
60
65
|
### --strict
|
|
61
66
|
warn_unused_configs = true
|
|
@@ -149,4 +154,3 @@ commands = pytest {posargs} --junitxml=test-reports/pytest.xml --cov="{envsitepa
|
|
|
149
154
|
# envsitepackagesdir see: https://tox.readthedocs.io/en/latest/example/pytest.html
|
|
150
155
|
|
|
151
156
|
"""
|
|
152
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Query bandwidth performance between two devices."""
|
|
2
|
+
|
|
2
3
|
import pytest
|
|
3
4
|
import json
|
|
4
5
|
from typing import Dict, Callable, Any
|
|
@@ -106,7 +107,6 @@ CONTEXT = IperfContext
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
class IperfResultError(Error):
|
|
109
|
-
|
|
110
110
|
"""Error in iperf result JSON."""
|
|
111
111
|
|
|
112
112
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|