Skip to content

Commit efa2dda

Browse files
committed
Merge branch 'master' into 58-write-wrapper-so-that-path-fspath-deprecation-warning-in-pytest-is-not-triggered
# Conflicts: # nuts/yamlloader.py # poetry.lock
2 parents 149a137 + 2b647c0 commit efa2dda

47 files changed

Lines changed: 229 additions & 200 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
2+
# https://www.ianwootten.co.uk/2020/10/23/publishing-to-pypi-using-github-actions/
3+
name: Publish nuts to PyPI and release on GitHub
4+
on: [push]
5+
jobs:
6+
build-and-publish-to-pypi:
7+
if: startsWith(github.ref, 'refs/tags')
8+
name: Build and publish nuts to PyPI
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v2
12+
- name: Set up Python 3.7
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: 3.7
16+
- name: Install poetry
17+
run: pip install poetry
18+
- name: Configure API token
19+
run: poetry config pypi-token.pypi "${{ secrets.PYPI_API_TOKEN }}"
20+
- name: Build and then publish nuts to PyPI
21+
run: poetry publish --build
22+
relase-on-github:
23+
if: startsWith(github.ref, 'refs/tags')
24+
name: Release on GitHub
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v2
28+
- name: Set tag value
29+
id: tag-val
30+
run: echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/}
31+
- name: Release on Github
32+
uses: softprops/action-gh-release@v1
33+
with:
34+
body_path: release_notes/${{ steps.tag-val.outputs.SOURCE_TAG }}.md
35+
env:
36+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
GITHUB_REPOSITORY: INSRapperswil/nuts

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ docs/build/
1111

1212
test-reports/
1313
.coverage
14+
15+
# ignore dists
16+
dist/

.gitlab-ci.yml

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
default:
2+
image: python:3.7
3+
before_script:
4+
- echo ===== update pip =====
5+
- python -m pip install -U pip
6+
- echo ===== install tox =====
7+
- pip install tox
8+
19
stages:
210
- pytest
311
- quality
@@ -13,16 +21,9 @@ cache:
1321

1422
pytest:
1523
stage: pytest
16-
image: python:3.7
17-
# several python versions in 1 image: quay.io/python-devs/ci-image
18-
before_script:
19-
- echo ===== update pip =====
20-
- python -m pip install -U pip
21-
- echo ===== install tox =====
22-
- pip install tox
24+
script:
2325
- echo ===== create folder for artifacts =====
2426
- mkdir test-reports
25-
script:
2627
- tox -e py
2728
artifacts:
2829
when: always
@@ -35,48 +36,25 @@ pytest:
3536
black:
3637
stage: quality
3738
image: python:3.7
38-
before_script:
39-
- echo ===== update pip =====
40-
- python -m pip install -U pip
41-
- echo ===== install tox =====
42-
- pip install tox
4339
script:
4440
- tox -e black
4541

4642
mypy:
4743
stage: quality
48-
image: python:3.7
49-
before_script:
50-
- echo ===== update pip =====
51-
- python -m pip install -U pip
52-
- echo ===== install tox =====
53-
- pip install tox
5444
script:
5545
- tox -e mypy
5646

5747
docs:
5848
stage: quality
59-
image: python:3.7
60-
before_script:
61-
- echo ===== update pip =====
62-
- python -m pip install -U pip
63-
- echo ===== install tox =====
64-
- pip install tox
6549
script:
6650
- tox -e docs
6751

6852
pytest-main:
6953
# run test using directly the pytest main branch
7054
stage: pytest
71-
image: python:3.7
72-
before_script:
73-
- echo ===== update pip =====
74-
- python -m pip install -U pip
75-
- echo ===== install tox =====
76-
- pip install tox
55+
script:
7756
- echo ===== create folder for artifacts =====
7857
- mkdir test-reports
79-
script:
8058
- tox -e pytest-main
8159
artifacts:
8260
when: always
@@ -89,12 +67,12 @@ pytest-main:
8967
- if: $CI_PIPELINE_SOURCE == "schedule"
9068

9169
sonar:
70+
image: sonarsource/sonar-scanner-cli
9271
stage: sonar
72+
before_script: []
9373
variables:
9474
SONAR_HOST_URL: $INS_SONAR_URL
9575
SONAR_LOGIN: $INS_SONAR_LOGIN
96-
image:
97-
name: sonarsource/sonar-scanner-cli
9876
script:
9977
- sonar-scanner -Dsonar.projectKey=$CI_PROJECT_NAME -Dsonar.branch.name=$CI_BUILD_REF_NAME -Dsonar.python.coverage.reportPaths=test-reports/coverage.xml
10078
allow_failure: true

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Institue of Networked Solutions (INS), Rapperswil/ NetTowel Project
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,26 @@ One major difference between unit tests in programming and
99
network tests is the definition of what a test actually is.
1010
In programming, unit tests normally focus on testing edge cases,
1111
since the amount of non-edge cases is not definable.
12-
In the network testing domain, tests are less about edge cases, but more about testing existing configurations with
12+
In the network testing domain, tests are less about edge cases, but more about testing existing network states with
1313
pre-defined test cases. Such a single test case might be "can host A reach neighbors X, Y, Z?" on many different devices.
1414
This is what nuts tries to achieve:
15-
Apply test cases based on your pre-defined network topology to your actual network and have the tests confirm the correct configuration.
15+
Apply test cases based on your pre-defined network topology to your actual network and have the tests confirm the correct state.
1616

1717
The project relies on the [pytest framework](https://docs.pytest.org/) to setup and execute the tests.
1818
Nuts itself is written as a custom pytest plugin. In the background, [nornir](https://nornir.readthedocs.io/)
1919
executes specific network tasks for the actual tests.
2020

21-
Additionally, nuts treats the test definition and the so-called test bundle as separate entities.
21+
Additionally, nuts treats the test definition and the so-called test bundle as separate entities. The *test definition* is modeled as a custom `pytest.Class`, and a predefined set of test definitions can be found in the module `base_tests`. New test definitions can be added easily by the user of the plugin.
2222

23-
The test definition is modelled as a custom `pytest.Class`, and a predefined set of test definitions can be found in the module `base_tests`. New test definitions can be added easily by the user of the plugin.
24-
25-
The test bundle is a file that is parsed by pytest. The file provides data on the actual network configuration and describes which test definitions should be collected and executed by pytest.
23+
The *test bundle* is a file that is parsed by pytest. The file provides data on the desired network state and describes which test definitions should be collected and executed by pytest.
2624
The structure of the test bundle should enable people without in-depth python knowledge to add new test bundles or update existing ones to reflect changes in the network.
2725

26+
While the readme here is only a short overview, find the [documentation of nuts on readthedocs](https://nuts.readthedocs.io/en/latest/).
27+
2828
## Test bundle structure
2929

3030
Currently only yaml files are supported as test bundles,
31-
but other sources such as other file formats or database entries can be considered in later versions.
31+
but other sources such as other file formats or database entries can be considered in later nuts versions.
3232

3333
Each test bundle contains the following structure:
3434
```yaml
@@ -50,11 +50,11 @@ Note that currently every test in this class will be executed.
5050
`label`: Additional identifier that can be used to distinguish between multiple occurrences of the same
5151
test class in a test bundle.
5252

53-
`test_execution`: Data that is exposed as part of the `nuts_parameters` property (explanation see below).
53+
`test_execution`: Data that is exposed as part of the `nuts_parameters` property.
5454
By convention this contains additional information that is passed directly to the nornir task in the background.
5555
Therefore the key-value pairs must be consistent with the key-value pairs of the specific nornir task.
5656
As an example, the test definition `napalm_ping.py` calls a nornir task to execute napalm's ping-command.
57-
This allows the additional `max_drop` parameter in `test execution`, since it is in turn pre-defined by napalm.
57+
This allows the additional `max_drop` parameter in `test_execution`, since it is in turn pre-defined by napalm.
5858

5959
`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.
6060

@@ -64,68 +64,60 @@ This example creates three different tests, one for each entry in the `test_data
6464

6565
```yaml
6666
---
67-
- test_module: pytest_nuts.base_tests.netmiko_cdp_neighbors
67+
- test_module: nuts.base_tests.netmiko_cdp_neighbors
6868
test_class: TestNetmikoCdpNeighbors
6969
test_data:
70-
- source: R1
70+
- host: R1
7171
local_port: GigabitEthernet3
7272
destination_host: R2
7373
management_ip: 172.16.12.2
7474
remote_port: GigabitEthernet2
75-
- source: R1
75+
- host: R1
7676
local_port: GigabitEthernet4
7777
destination_host: R3
7878
management_ip: 172.16.13.3
7979
remote_port: GigabitEthernet2
80-
- source: R2
80+
- host: R2
8181
local_port: GigabitEthernet2
8282
destination_host: R1
8383
management_ip: 172.16.12.1
8484
remote_port: GigabitEthernet3
8585
...
8686
```
8787

88-
## Installation instructions
89-
NetTowel nuts is currently not published via pip. It has to be cloned and installed manually.
88+
## Manual installation instructions
89+
Nuts uses [poetry](https://python-poetry.org/) as a dependency manager.
9090

91-
```
92-
git clone ssh://git@gitlab.ost.ch:45022/ins/nettowel/nettowel-nuts.git
93-
pip install <your_nuts_directory>
94-
```
91+
1. [Install poetry](https://python-poetry.org/docs/#installation).
92+
2. Clone this repository.
93+
3. Run `$ poetry install`
9594

9695
## Technical Overview
9796

98-
### Nuts custom marker
99-
100-
During test collection, the custom pytest marker "nuts" uses the data that has been defined in the test bundle mentioned above.
101-
This annotation is a wrapper around the `pytest.mark.parametrize` annotation and allows the plugin to use the data entries
102-
from the test bundle. For each entry in the `test_data` section of the test bundle, the custom marker generates a single test case.
103-
Each entry is a dictionary, but `pytest.mark.parametrize` expects a list of n-tuples as input.
104-
The plugin therefore transforms those dictionary entries from `test_data` into tuples.
105-
This transformation is currently fixed, but more flexibility is very likely to come at a later stage.
97+
When nuts is executed, pytest converts the test bundles (the yaml files) into tests. During test collection, the custom pytest marker `nuts` uses the data that has been defined in the test bundle mentioned above.
98+
This annotation is a wrapper around the `pytest.mark.parametrize` annotation and allows the plugin to use the data entries from the test bundle. For each entry in the `test_data` section of the test bundle, the custom marker generates a single test case. To achieve this, the plugin transforms the entries into n-tuples, since `pytest.mark.parametrize` expects a list of n-tuples as input.
10699

107100
The custom nuts marker takes two arguments: The first argument of the annotation determines the required fields.
108101
For each entry in `test_data` these fields are extracted and transformed to a tuple considering the correct order.
109102
If any of these fields are not present in an entry of `test_data`, the corresponding test case will be skipped.
110-
The second argument determines optional fields that can also be used in a test case as well - non-present values are passed into the function as `None`.
103+
A second argument determines optional fields that can also be used in a test case as well - non-present values are passed into the function as `None`.
111104

112105
#### Example of a test class with custom marker
113106

114107
```python
115108
@pytest.mark.usefixtures("check_nuts_result") # see below
116-
class TestNetmikoCdpNeighbors:
117-
@pytest.mark.nuts("host,remote_host,local_port,management_ip", "management_ip")
109+
class TestNetmikoCdpNeighbors:
110+
@pytest.mark.nuts("host,remote_host,local_port")
118111
def test_local_port(self, single_result, remote_host, local_port):
119-
assert single_result.result[remote_host]["local_port"] == local_port
112+
assert single_result.result[remote_host]["local_port"] == local_port
120113
```
121114

122-
This test run of CDP neighbors checks the local port.
115+
This test-run of CDP neighbors checks the local port.
123116

124117
Before each test evaluation, the fixture `@pytest.mark.usefixtures("check_nuts_result")` checks the result of the network information that has been gathered by nornir in the background: It asserts that that no exception was thrown while doing so.
125118

126119
The required fields are `host`, `remote_host` and `local_port` - they must be present in the custom marker,
127120
but also be provided as argument to the test method itself.
128-
`management_ip` is an optional field and not necessary for the test - an entry in `test_data` is not required to have it.
129121

130122
`single_result` uses the `host` field and provides the result that has been processed via the specific context of a test.
131123

@@ -136,40 +128,13 @@ Currently, the predefined test classes use [nornir](https://nornir.readthedocs.i
136128
with the network devices, therefore the test classes derive all from a more specific `NornirNutsContext`,
137129
which provides a nornir instance and nornir-specific helpers.
138130

139-
This context class is then integrated into pytest fixtures. It is passed on to another fixture called `sincle_result` that returns a per-host result of one test.
140-
141131
## Development
142-
Nuts uses [poetry](https://python-poetry.org/) as a dependency manager.
143-
If you have not installed poetry, please read their [installation instructions](https://python-poetry.org/docs/#installation).
144-
145-
### Installation requirements
146132

147-
```bash
148-
poetry install
149-
```
150-
151-
### Open shell with venv
152-
153-
```bash
154-
poetry shell
155-
```
156-
157-
### SonarQube
158-
Our [sonarqube server](sonarqube.ins.work) automatically analyses our project via Gitlab CI.
159-
If you prefer to run your analysis without pushing, you can trigger the analysis locally after executing the tests with coverage.
160-
161-
Windows PowerShell:
162-
```bash
163-
$token=<your_token>
164-
docker run --volume ${pwd}:/usr/src --workdir /usr/src --rm -e SONAR_HOST_URL="https://sonarqube.ins.work" -e SONAR_LOGIN=$token sonarsource/sonar-scanner-cli "-Dsonar.projectKey=nettowel-nuts" "-Dsonar.branch.name=$(git rev-parse --abbrev-ref HEAD)" "-Dsonar.python.coverage.reportPaths=/usr/src/test-reports/coverage.xml"
165-
```
166-
UNIX:
167-
```bash
168-
token=<your_token>
169-
docker run --volume $PWD:/usr/src --workdir /usr/src --rm -e SONAR_HOST_URL="https://sonarqube.ins.work" -e SONAR_LOGIN=$token sonarsource/sonar-scanner-cli -Dsonar.projectKey=nettowel-nuts -Dsonar.branch.name=$(git rev-parse --abbrev-ref HEAD) -Dsonar.python.coverage.reportPaths=/usr/src/test-reports/coverage.xml
170-
```
133+
Nuts is essentially designed as a pytest-plugin and it is possible to add your own, self-written test classes.
134+
A dev documentation on how to write your own test classes is planned for the future.
135+
Until then, please read the regular [documentation of nuts](https://nuts.readthedocs.io/en/latest/) on how to use it.
171136

172137
# Thanks
173138

174-
* [Matthias Gabriel](https://github.com/MatthiasGabriel), who laid the foundations of NUTS.
139+
* [Matthias Gabriel](https://github.com/MatthiasGabriel), who laid the foundations of nuts.
175140
* [Florian Bruhin](https://github.com/The-Compiler) for invaluable feedback.

docs/source/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ Enhance NUTS
4242
Since NUTS is written as a pytest plugin and in python, you can customize it yourself and write your own test classes. Please see the :doc:`development section <dev/index>` to see how NUTS is structured and how to write your own test classes.
4343

4444

45-
4645
Contents
4746
========
4847

docs/source/installation/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ NUTS is currently not published on the Python Package Index (`PyPI <https://pypi
2525
# install NUTS
2626
pip install <your_nuts_directory>
2727
28+
# install NUTS directly from git without clone first
29+
pip install git+https://github.com/INSRapperswil/Nuts.git
30+
2831
Parsing with ntc-templates
2932
--------------------------
3033

docs/source/tutorial/firststeps.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
First Steps with NUTS
22
=====================
33

4-
This tutorial guides you through a minimal setup of NUTS.
4+
This tutorial guides you through a minimal setup of the NetTowel Unit Testing System (NUTS, or nuts).
55

6-
Two major components are needed for NUTS:
6+
Two major components are needed for nuts:
77

88
#. A network inventory
99
#. Test bundles
@@ -71,7 +71,7 @@ If you set up the above folders and files, you're ready to write test bundles.
7171

7272
A test bundle is a collection of tests are logically related to each other, for example tests that all revolve around "information on BGP neighbors". The test bundle describes which test definition should be collected and executed and provides data for those tests. The bundles are written as individual entries in a YAML file.
7373

74-
Currently only YAML files are supported as test bundle format, but other data sources could be integrated in later versions of NUTS.
74+
Currently only YAML files are supported as test bundle format, but other data sources could be integrated in later versions of nuts.
7575

7676
Structure of a Test Bundle
7777
**************************
@@ -93,7 +93,7 @@ Each test bundle contains the following structure:
9393
``label``: Optional. Additional identifier that can be used to distinguish between multiple occurrences of the same
9494
test class in a test bundle.
9595

96-
``test_execution``: Optional. NUTS uses nornir tasks to automatically interact with the network. This field contains additional information that is directly passed to the nornir task in the background. Therefore the key-value pairs must be consistent with the key-value pairs of the specific nornir task.
96+
``test_execution``: Optional. Nuts uses nornir tasks to automatically interact with the network. This field contains additional information that is directly passed to the nornir task in the background. Therefore the key-value pairs must be consistent with the key-value pairs of the specific nornir task.
9797
As an example, the test definition ``TestNapalmPing`` calls a nornir task to execute napalm's ping-command.
9898
This allows the additional ``count`` parameter in ``test execution``, since it is in turn pre-defined by napalm. Please see the :doc:`chapter on test bundles <../testbundles/alltestbundles>` for more detailed explanations.
9999

0 commit comments

Comments
 (0)