Skip to content

Commit c8eadd4

Browse files
authored
Merge pull request #23 from romecode/main
add min_version and min_change_number
2 parents 7e1c391 + d0a27d3 commit c8eadd4

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Netdut: automated software testing for switches using pytest
22
============================================================
33

4-
![PyPi](https://img.shields.io/pypi/v/pytest-netdut.svg)
4+
![PyPi](https://img.shields.io/pypi/v/pytest-netdut.svg)
55
![Python Versions](https://img.shields.io/pypi/pyversions/pytest-netdut.svg)
6-
![collection version](https://img.shields.io/github/v/release/aristanetworks/pytest-netdut)
6+
![collection version](https://img.shields.io/github/v/release/aristanetworks/pytest-netdut)
77
![License](https://img.shields.io/github/license/aristanetworks/pytest-netdut)
88

99
Netdut is a [pytest](https://docs.pytest.org/) plugin which provides infrastructure (e.g. pytest fixtures) which make it easy to write automated tests for network switches.
@@ -13,8 +13,8 @@ Features
1313
--------
1414

1515
* Brings the power, maturity and ecosystem of pytest to testing on network switches.
16-
* The `dut` fixture (Device Under Test) providing serial, ssh or [EAPI](https://github.com/arista-eosplus/pyeapi) connectivity for running CLI commands on a network switch.
17-
* Command-line configuration of a hostname and console name.
16+
* The `dut` fixture (Device Under Test) providing serial, ssh or [EAPI](https://github.com/arista-eosplus/pyeapi) connectivity for running CLI commands on a network switch.
17+
* Command-line configuration of a hostname and console name.
1818
* Markers for skipping tests based on the device's type or software configuration.
1919
* Compatibility with both Arista's EOS operating system, and the Metamako MOS operating system.
2020
* A pythonic interfaces for writing EOS CLI commands.
@@ -156,6 +156,18 @@ def test_that_only_runs_on_eos_on_7130(dut):
156156
logging.info("Must be EOS on 7130!")
157157
```
158158

159+
OS decorators accept the following keywords:
160+
- min_version (string)
161+
- min_change_number (int)
162+
163+
If both kwargs are specified, min_change_number takes precedence.
164+
```python
165+
@pytest.mark.eos(min_version="4.30.0", min_change_number=3452345)
166+
@pytest.mark.skip_device_type("DCS-7130.*")
167+
def test_that_only_runs_on_eos_on_7130(dut):
168+
logging.info("Must be EOS on 7130!")
169+
```
170+
159171
### Building test harnesses
160172

161173
Pytest's fixture mechanism is very powerful in this scenario, allowing us to set up and tear
@@ -216,7 +228,7 @@ to snake_case.
216228
The translator has a predefined set of translations which can be extended by subclassing the Translator class and overriding `config_patterns`.
217229
Return values are processed by the `translate_key` function which must be defined in the subclass.
218230

219-
Set the new translator class via `eapi.set_translator(<class instance>)`.
231+
Set the new translator class via `eapi.set_translator(<class instance>)`.
220232

221233
Contributing
222234
------------

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ install_requires =
4141
pyeapi>=0.8.4
4242
pytest>=3.5.0
4343
six>=1.15
44+
packaging>=22.0
4445

4546
[options.packages.find]
4647
where=src

src/pytest_netdut/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def create(name) -> Callable:
144144
factories.create_console_url_fixture(name),
145145
factories.create_skipper_fixture(name),
146146
factories.create_sku_fixture(name),
147+
factories.create_os_version_fixture(name),
147148
factories.create_softened_fixture(name),
148149
factories.create_ssh_fixture(name),
149150
factories.create_xapi_fixture(name),

src/pytest_netdut/factories.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import logging
1818
import re
1919
import pytest
20+
from packaging import version
2021
from .wrappers import CLI, xapi
2122

2223
logger = logging.getLogger(__name__)
@@ -72,16 +73,46 @@ def _console_url(request):
7273
return _console_url
7374

7475

76+
def parse_version(v):
77+
"""Return series of dot separated digits from beginning of string
78+
as release and the last group delimited by - as change number."""
79+
_regex = r"""
80+
(?P<release>[0-9]+(?:\.[0-9]+)*)
81+
(?:
82+
[-]?
83+
(?P<change_number>[\w]+)
84+
)*
85+
"""
86+
_regex = re.compile(_regex, re.VERBOSE)
87+
# _regex = r"(?P<release>[\d\.]*)"
88+
parsed = re.match(_regex, v)
89+
return (parsed.group("release"), parsed.group("change_number"))
90+
91+
92+
def version_skipper(found, expected):
93+
try:
94+
found = version.Version(str(found))
95+
expected = version.Version(str(expected))
96+
if found < expected:
97+
pytest.skip(f"min_version {expected} not satisfied: {found}")
98+
except version.InvalidVersion:
99+
logging.error("Could not parse versions: %s < %s", found, expected)
100+
101+
75102
def create_skipper_fixture(name):
76103
@pytest.fixture(scope="session", name=f"{name}_skipper")
77104
def _skipper(request):
78105
def skipper(node):
79106
allowed_os = set()
107+
min_version = None
108+
min_chg_num = None
80109

81110
# Restrict SKUs
82111
for marker in node.iter_markers():
83112
if marker.name in {"mos", "eos"}:
84113
allowed_os.add(marker.name)
114+
min_chg_num = marker.kwargs.get("min_change_number", None)
115+
min_version = marker.kwargs.get("min_version", None)
85116

86117
elif marker.name == "only_device_type":
87118
pattern = marker.args[0]
@@ -97,8 +128,16 @@ def skipper(node):
97128

98129
if allowed_os:
99130
dut_ssh = request.getfixturevalue(f"{name}_ssh")
131+
dut_os_version = request.getfixturevalue(f"{name}_os_version")
100132
if dut_ssh.cli_flavor not in allowed_os:
101-
pytest.skip(f"cannot run on platform {dut_ssh.cli_flavor}")
133+
pytest.skip(f"Cannot run on platform {dut_ssh.cli_flavor}")
134+
if min_version or min_chg_num:
135+
# matches the pattern X.XX.X.XX with digits only
136+
release, change_number = parse_version(dut_os_version)
137+
if min_chg_num:
138+
version_skipper(change_number, min_chg_num)
139+
else:
140+
version_skipper(release, min_version)
102141

103142
return skipper
104143

@@ -118,6 +157,19 @@ def _sku(request):
118157
return _sku
119158

120159

160+
def create_os_version_fixture(name):
161+
@pytest.fixture(scope="session", name=f"{name}_os_version")
162+
def _os_version(request):
163+
ssh = request.getfixturevalue(f"{name}_ssh")
164+
assert ssh.cli_flavor in {"eos", "mos"}
165+
output = ssh.sendcmd("show version", timeout=300)
166+
matcher = re.search(r"Software image version: (\S*)", output)
167+
logging.info("Got OS version: %s", matcher.group(1))
168+
yield matcher.group(1)
169+
170+
return _os_version
171+
172+
121173
def create_softened_fixture(name):
122174
@pytest.fixture(scope="session", name=f"{name}_softened")
123175
def _softened(dut_ssh):

0 commit comments

Comments
 (0)