From cb622511cbefbaa802ffd8c99741f6581be51e56 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Tue, 19 Jul 2022 07:17:07 +0100 Subject: [PATCH 01/15] new: dev: Add testing against the latest Python packages. --- .github/workflows/ci.yml | 5 ++- .github/workflows/latest.yml | 36 +++++++++++++++++++ .yamllint.yml | 1 + tests/features/example.feature | 4 +-- tests/resources/requirements-latest.txt | 48 +++++++++++++++++++++++++ tests/resources/sut/Dockerfile | 2 +- 6 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/latest.yml create mode 100644 tests/resources/requirements-latest.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f13f158..4376e4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,8 @@ name: CI on: push: - pull_request: - branches: - - main + branches-ignore: + - 'feature/latest' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml new file mode 100644 index 0000000..cdac27b --- /dev/null +++ b/.github/workflows/latest.yml @@ -0,0 +1,36 @@ +--- +name: Latest + +on: + push: + branches: + - feature/latest + schedule: + - cron: '0 0 1,15 * *' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + + - name: Requirements + run: pip install -r tests/resources/requirements-latest.txt + + - name: Bandit + run: bandit -r . + + - name: Test + run: make test + + - name: Build + run: make build diff --git a/.yamllint.yml b/.yamllint.yml index be2ecac..7b2e6e2 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -31,4 +31,5 @@ rules: level: warning ignore: | .github/workflows/ci.yml + .github/workflows/latest.yml .github/workflows/publish.yml diff --git a/tests/features/example.feature b/tests/features/example.feature index a493d64..ea3cd76 100644 --- a/tests/features/example.feature +++ b/tests/features/example.feature @@ -84,7 +84,7 @@ Feature: Example of Testinfra BDD When the pip package is testinfra-bdd # Can check if the package is absent or present. Then the pip package is present - And the pip package version is 1.0.0 + And the pip package version is 1.0.6 # Check that installed packages have compatible dependencies. And the pip check is OK @@ -109,7 +109,7 @@ Feature: Example of Testinfra BDD Given the host with URL "docker://sut" is ready When the pip package is Then the pip package is present - # And the pip package is latest + And the pip package is latest Examples: | pip_package | | pytest-bdd | diff --git a/tests/resources/requirements-latest.txt b/tests/resources/requirements-latest.txt new file mode 100644 index 0000000..f95ddde --- /dev/null +++ b/tests/resources/requirements-latest.txt @@ -0,0 +1,48 @@ +# This file should contain all the packages in the requirements.txt file in +# the root directory of this project except with no version specification. +attrs +bandit +build +colorama +coverage +flake8 +flake8-docstrings +flake8-quotes +future +gitchangelog +gitdb +GitPython +glob2 +iniconfig +Mako +mando +MarkupSafe +mccabe +packaging +parse +parse-type +pathspec +pbr +pep517 +pluggy +py +pycodestyle +pydocstyle +pyflakes +pyparsing +pystache +pytest +pytest-bdd +pytest-cov +pytest-flake8 +pytest-testinfra +PyYAML +radon +semver +six +smmap +snowballstemmer +stevedore +tomli +urllib3 +yamllint diff --git a/tests/resources/sut/Dockerfile b/tests/resources/sut/Dockerfile index eb254bb..a11d5dc 100644 --- a/tests/resources/sut/Dockerfile +++ b/tests/resources/sut/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && chmod 544 /etc/ntp.conf \ && chown ntp:ntp /etc/ntp.conf \ - && pip install --no-cache-dir semver==2.12.0 testinfra-bdd==1.0.0 + && pip install --no-cache-dir semver==2.12.0 testinfra-bdd==1.0.6 COPY issue21.txt /tmp From 1b13841c8a9abf9a23259a98e2311d752efb6b06 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Tue, 19 Jul 2022 07:23:43 +0100 Subject: [PATCH 02/15] fix: dev: Stop Pip install being so verbose. --- .github/workflows/ci.yml | 2 +- .github/workflows/latest.yml | 4 +++- .github/workflows/publish.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4376e4f..7634c57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: python-version: '3.x' - name: Requirements - run: pip install -r requirements.txt + run: pip install -qr requirements.txt - name: Bandit run: bandit -r . diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index cdac27b..0344e49 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -24,7 +24,9 @@ jobs: python-version: '3.x' - name: Requirements - run: pip install -r tests/resources/requirements-latest.txt + run: | + pip install -qr tests/resources/requirements-latest.txt + pip freeze - name: Bandit run: bandit -r . diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 83c639b..ffb229b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt + pip install -qr requirements.txt - name: Build run: make build From 71a99ec4df64bcc4a946f30773b373a769a2de7b Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Tue, 19 Jul 2022 07:30:55 +0100 Subject: [PATCH 03/15] fix: dev: Update Python packages. --- requirements.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9d4fe52..cb67941 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ attrs==21.4.0 bandit==1.7.4 -build==0.7.0 +build==0.8.0 colorama==0.4.5 -coverage==6.3.2 +coverage==6.4.2 flake8==4.0.1 flake8-docstrings==1.6.0 flake8-quotes==3.3.1 @@ -12,7 +12,7 @@ gitdb==4.0.9 GitPython==3.1.27 glob2==0.7 iniconfig==1.1.1 -Mako==1.2.0 +Mako==1.2.1 mando==0.6.4 MarkupSafe==2.1.1 mccabe==0.6.1 @@ -27,20 +27,20 @@ py==1.11.0 pycodestyle==2.8.0 pydocstyle==6.1.1 pyflakes==2.4.0 -pyparsing==3.0.8 +pyparsing==3.0.9 pystache==0.6.0 pytest==7.1.2 pytest-bdd==6.0.1 pytest-cov==3.0.0 pytest-flake8==1.1.1 -pytest-testinfra==6.7.0 +pytest-testinfra==6.8.0 PyYAML==6.0 radon==5.1.0 semver==2.13.0 six==1.16.0 smmap==5.0.0 snowballstemmer==2.2.0 -stevedore==3.5.0 +stevedore==4.0.0 tomli==2.0.1 -urllib3==1.26.9 -yamllint==1.26.3 +urllib3==1.26.10 +yamllint==1.27.1 From b64217e7f50d8c25cb3d6d24bd2001b109df6edf Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Tue, 19 Jul 2022 07:31:38 +0100 Subject: [PATCH 04/15] chg: dev: Run "pip check" after installing Python packages. --- .github/workflows/ci.yml | 5 ++++- .github/workflows/latest.yml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7634c57..6c9455e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,10 @@ jobs: python-version: '3.x' - name: Requirements - run: pip install -qr requirements.txt + run: | + pip install -qr requirements.txt + pip freeze + pip check - name: Bandit run: bandit -r . diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index 0344e49..412fa34 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -27,6 +27,7 @@ jobs: run: | pip install -qr tests/resources/requirements-latest.txt pip freeze + pip check - name: Bandit run: bandit -r . From 3204cea8fc387fbc890c20b05188df9f82aeb8da Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Wed, 20 Jul 2022 06:42:15 +0100 Subject: [PATCH 05/15] chg: Add the Snyk badge and reduce the size of the step functions. --- README.md | 92 ++------------------------------ tests/step_defs/test_example.py | 94 ++------------------------------- 2 files changed, 9 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index ac22915..a03e2dc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/locp/testinfra-bdd.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/locp/testinfra-bdd/context:python) [![Maintainability](https://api.codeclimate.com/v1/badges/5482c55d78b369a0a55e/maintainability)](https://codeclimate.com/github/locp/testinfra-bdd/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/5482c55d78b369a0a55e/test_coverage)](https://codeclimate.com/github/locp/testinfra-bdd/test_coverage) +[![testinfra-bdd](https://snyk.io/advisor/python/testinfra-bdd/badge.svg)](https://snyk.io/advisor/python/testinfra-bdd) An interface between [pytest-bdd](https://pytest-bdd.readthedocs.io/en/latest/) @@ -184,100 +185,15 @@ Feature: Example of Testinfra BDD and `tests/step_defs/test_example.py` contains the following: ```python -""" -Examples of step definitions for Testinfra BDD feature tests. +"""Examples of step definitions for Testinfra BDD feature tests.""" +from pytest_bdd import scenarios -Notes ------ -The user must define their scenarios in a way similar to below. However, the -scenarios can be empty. -""" +scenarios('../features/example.feature') -from pytest_bdd import scenario # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. pytest_plugins = ['testinfra_bdd'] - - -@scenario('../features/example.feature', 'Check Java 11 is Installed') -def test_check_java_11_is_installed(): - """Check Java 11 is Installed.""" - - -@scenario('../features/example.feature', 'Check Java is Installed in the Path') -def test_check_java_is_installed_in_the_path(): - """Check Java is Installed in the Path.""" - - -@scenario('../features/example.feature', 'Check Network Address') -def test_check_network_address(): - """Check Network Address.""" - - -@scenario('../features/example.feature', 'Check Network Address With Port') -def test_check_network_address_with_port(): - """Check Network Address With Port.""" - - -@scenario('../features/example.feature', 'Check Sockets') -def test_check_sockets(): - """Check Sockets.""" - - -@scenario('../features/example.feature', 'File Checks') -def test_file_checks(): - """File Checks.""" - - -@scenario('../features/example.feature', 'Group Checks') -def test_group_checks(): - """Group Checks.""" - - -@scenario('../features/example.feature', 'Python Package') -def test_python_package(): - """Python Package.""" - - -@scenario('../features/example.feature', 'Running Commands') -def test_running_commands(): - """Running Commands.""" - - -@scenario('../features/example.feature', 'Service Checks') -def test_service_checks(): - """Service Checks.""" - - -@scenario('../features/example.feature', 'Skip Tests if Host is Windoze') -def test_skip_tests_if_host_is_windoze(): - """Skip Tests if Host is Windoze.""" - - -@scenario('../features/example.feature', 'System Package') -def test_system_package(): - """System Package.""" - - -@scenario('../features/example.feature', 'Test Pip Packages are Latest Versions') -def test_test_pip_packages_are_latest_versions(): - """Test Pip Packages are Latest Versions.""" - - -@scenario('../features/example.feature', 'Test Running Processes') -def test_test_running_processes(): - """Test Running Processes.""" - - -@scenario('../features/example.feature', 'Test for Absent Resources') -def test_test_for_absent_resources(): - """Test for Absent Resources.""" - - -@scenario('../features/example.feature', 'User Checks') -def test_user_checks(): - """User Checks.""" ``` ## "Given" Steps diff --git a/tests/step_defs/test_example.py b/tests/step_defs/test_example.py index 97e8ce3..efadd16 100644 --- a/tests/step_defs/test_example.py +++ b/tests/step_defs/test_example.py @@ -1,16 +1,12 @@ -""" -Examples of step definitions for Testinfra BDD feature tests. - -Notes ------ -The user must define their scenarios in a way similar to below. However, the -scenarios can be empty. -""" +"""Examples of step definitions for Testinfra BDD feature tests.""" import os import pytest from pytest_bdd import given -from pytest_bdd import scenario +from pytest_bdd import scenarios + +scenarios('../features/example.feature') + # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. @@ -22,83 +18,3 @@ def on_github_actions_we_skip_tests(): """on GitHub Actions we skip tests.""" if 'GITHUB_ACTIONS' in os.environ and os.environ['GITHUB_ACTIONS'] == 'true': pytest.skip('GitHub Actions does not support Ping/ICMP.') - - -@scenario('../features/example.feature', 'Check Java 11 is Installed') -def test_check_java_11_is_installed(): - """Check Java 11 is Installed.""" - - -@scenario('../features/example.feature', 'Check Java is Installed in the Path') -def test_check_java_is_installed_in_the_path(): - """Check Java is Installed in the Path.""" - - -@scenario('../features/example.feature', 'Check Network Address') -def test_check_network_address(): - """Check Network Address.""" - - -@scenario('../features/example.feature', 'Check Network Address With Port') -def test_check_network_address_with_port(): - """Check Network Address With Port.""" - - -@scenario('../features/example.feature', 'Check Sockets') -def test_check_sockets(): - """Check Sockets.""" - - -@scenario('../features/example.feature', 'File Checks') -def test_file_checks(): - """File Checks.""" - - -@scenario('../features/example.feature', 'Group Checks') -def test_group_checks(): - """Group Checks.""" - - -@scenario('../features/example.feature', 'Python Package') -def test_python_package(): - """Python Package.""" - - -@scenario('../features/example.feature', 'Running Commands') -def test_running_commands(): - """Running Commands.""" - - -@scenario('../features/example.feature', 'Service Checks') -def test_service_checks(): - """Service Checks.""" - - -@scenario('../features/example.feature', 'Skip Tests if Host is Windoze') -def test_skip_tests_if_host_is_windoze(): - """Skip Tests if Host is Windoze.""" - - -@scenario('../features/example.feature', 'System Package') -def test_system_package(): - """System Package.""" - - -@scenario('../features/example.feature', 'Test Pip Packages are Latest Versions') -def test_test_pip_packages_are_latest_versions(): - """Test Pip Packages are Latest Versions.""" - - -@scenario('../features/example.feature', 'Test Running Processes') -def test_test_running_processes(): - """Test Running Processes.""" - - -@scenario('../features/example.feature', 'Test for Absent Resources') -def test_test_for_absent_resources(): - """Test for Absent Resources.""" - - -@scenario('../features/example.feature', 'User Checks') -def test_user_checks(): - """User Checks.""" From a3b3b943e0481f8f3e11fab9f5bf3fb0b6dedf3c Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Wed, 20 Jul 2022 07:35:47 +0100 Subject: [PATCH 06/15] fix: doc: Refactor Markdown. --- CODE_OF_CONDUCT.md | 8 ++++---- CONTRIBUTING.md | 7 +++++-- README.md | 4 ++-- tests/features/example.feature | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3833a45..fcd08e1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -https://github.com/locp/testinfra-bdd/issues. +info@locp.co.uk. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the @@ -116,7 +116,7 @@ the community. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). @@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +. Translations are available at +. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b00f1e6..4f3ed6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,11 +25,12 @@ Things that will make your branch more likely to be pulled: Tests are run against a branch pushes and pull requests using GitHub Workflows for this project these are visible at -https://github.com/locp/testinfra-bdd/actions + ## Cutting a Release Ensure your local repo is up-to-date: + ```shell git checkout develop git fetch -p origin && git pull && git pull --tags @@ -38,6 +39,7 @@ git pull ``` Now set a shell variable to help us create the release: + ```shell RELEASE='0.1.0' git flow release start $RELEASE @@ -47,6 +49,7 @@ Now edit `testinfra_bdd/__init__.py` and ensure that the `__version__` variable is set to the same value as `$RELEASE`. Now update the `CHANGELOG.md` with: + ```shell make changelog ``` @@ -66,7 +69,7 @@ git flow finish -m "v${RELEASE}" -p ``` When all the CI jobs have completed, create the new release at -https://github.com/locp/testinfra-bdd/releases + ### Post Release Steps diff --git a/README.md b/README.md index a03e2dc..2ade140 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ Feature: Example of Testinfra BDD Given the host with URL "docker://sut" is ready When the user is "ntp" Then the user is present - And the user state is present # Alternative method of checking the state of a resource. + # Alternative method of checking the state of a resource. + And the user state is present And the user group is ntp And the user uid is 101 And the user gid is 101 @@ -259,7 +260,6 @@ allow the user to either skip tests if the host does not match an expected profile. They also allow the user to specify which resource or is to be tested. - ### Skip Tests if Host Profile Does Not Match It may be useful to skip tests if you find that the system under test doesn't diff --git a/tests/features/example.feature b/tests/features/example.feature index ea3cd76..d5900c3 100644 --- a/tests/features/example.feature +++ b/tests/features/example.feature @@ -34,7 +34,8 @@ Feature: Example of Testinfra BDD Given the host with URL "docker://sut" is ready When the user is "ntp" Then the user is present - And the user state is present # Alternative method of checking the state of a resource. + # Alternative method of checking the state of a resource. + And the user state is present And the user group is ntp And the user uid is 101 And the user gid is 101 From 404f2e5f217615faa4cebf75d99824bc03c83895 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 11:55:11 +0100 Subject: [PATCH 07/15] new: dev: Add tests for checking Python file size. --- .codeclimate.yml | 73 +++++++++++++++++++++++++++ tests/features/lines_of_code.feature | 8 +++ tests/step_defs/test_issue21.py | 9 ++-- tests/step_defs/test_lines_of_code.py | 48 ++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 .codeclimate.yml create mode 100644 tests/features/lines_of_code.feature create mode 100644 tests/step_defs/test_lines_of_code.py diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..a6beeba --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,73 @@ +--- +version: "2" # required to adjust maintainability checks + +checks: + argument-count: + enabled: true + config: + threshold: 4 + complex-logic: + enabled: true + config: + threshold: 4 + file-lines: + enabled: true + config: + threshold: 250 + method-complexity: + enabled: true + config: + threshold: 5 + method-count: + enabled: true + config: + threshold: 20 + method-lines: + enabled: true + config: + threshold: 25 + nested-control-flow: + enabled: true + config: + threshold: 4 + return-statements: + enabled: true + config: + threshold: 4 + similar-code: + enabled: true + config: + threshold: # language-specific defaults. overrides affect all languages. + identical-code: + enabled: true + config: + threshold: # language-specific defaults. overrides affect all languages. + +plugins: + bandit: + enabled: true + markdownlint: + enabled: true + radon: + enabled: true + sonar-python: + enabled: true + config: + tests_patterns: + - tests/** + +exclude_patterns: + - "config/" + - "db/" + - "dist/" + - "features/" + - "**/node_modules/" + - "script/" + - "**/spec/" + - "**/test/" + - "**/tests/" + - "Tests/" + - "**/vendor/" + - "**/*_test.go" + - "**/*.d.ts" + - "CHANGELOG.md" # This file is auto-generated. diff --git a/tests/features/lines_of_code.feature b/tests/features/lines_of_code.feature new file mode 100644 index 0000000..99827c3 --- /dev/null +++ b/tests/features/lines_of_code.feature @@ -0,0 +1,8 @@ +Feature: Lines of Code + Scenario Outline: Check Lines of Code in Radon Report + Given a Radon report + When Python source file is file + Then lines of code must not be greater than + Examples: + | max_lines_of_code | + | 250 | diff --git a/tests/step_defs/test_issue21.py b/tests/step_defs/test_issue21.py index c3bc7ad..402a173 100644 --- a/tests/step_defs/test_issue21.py +++ b/tests/step_defs/test_issue21.py @@ -1,12 +1,9 @@ """Fix Issue 21 feature tests.""" -from pytest_bdd import scenario +from pytest_bdd import scenarios + +scenarios('../features/issue21.feature') # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. pytest_plugins = ['testinfra_bdd'] - - -@scenario('../features/issue21.feature', 'Issue 21') -def test_issue_21(): - """Issue 21.""" diff --git a/tests/step_defs/test_lines_of_code.py b/tests/step_defs/test_lines_of_code.py new file mode 100644 index 0000000..cdeb8d0 --- /dev/null +++ b/tests/step_defs/test_lines_of_code.py @@ -0,0 +1,48 @@ +"""Lines of Code feature tests.""" +import json +import pytest +import subprocess + +from pytest_bdd import ( + given, + scenario, + then, + when, + parsers +) + +radon_report = subprocess.run(['radon', 'raw', '--json', '.'], capture_output=True) +radon_report = json.loads(radon_report.stdout) +python_files = [] + +for _ in radon_report: + python_files.append((_,)) + + +@pytest.mark.parametrize( + ["python_file"], + python_files, +) +@scenario('../features/lines_of_code.feature', 'Check Lines of Code in Radon Report') +def test_check_lines_of_code_in_radon_report(python_file): + """Check Lines of Code in Radon Report.""" + + +@given('a Radon report', target_fixture='radon_stats') +def a_radon_report(python_file): + """a Radon report.""" + return {} + + +@when('Python source file is file') +def python_source_file_is_file(python_file, radon_stats): + """Python source file is file.""" + radon_stats['file_name'] = python_file + radon_stats['lines_of_code'] = radon_report[python_file]['loc'] + + +@then(parsers.parse('lines of code must not be greater than {max_lines_of_code:d}')) +def lines_of_code_must_not_be_greater_than_max_lines_of_code(max_lines_of_code, radon_stats): + """lines of code must not be greater than .""" + message = f'{radon_stats["file_name"]} has {radon_stats["lines_of_code"]} which exceeds {max_lines_of_code}.' + assert radon_stats['lines_of_code'] <= max_lines_of_code, message From 9f69288533315594ab10d466484e104d05cd8134 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 12:55:16 +0100 Subject: [PATCH 08/15] fix: dev: Fix bandit related issues. --- .bandit | 2 +- tests/step_defs/test_lines_of_code.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.bandit b/.bandit index f783118..dfef5a6 100644 --- a/.bandit +++ b/.bandit @@ -1,2 +1,2 @@ [bandit] -skips: B101 +skips: B101,B404 diff --git a/tests/step_defs/test_lines_of_code.py b/tests/step_defs/test_lines_of_code.py index cdeb8d0..009f748 100644 --- a/tests/step_defs/test_lines_of_code.py +++ b/tests/step_defs/test_lines_of_code.py @@ -1,4 +1,5 @@ """Lines of Code feature tests.""" +import shutil import json import pytest import subprocess @@ -11,8 +12,16 @@ parsers ) -radon_report = subprocess.run(['radon', 'raw', '--json', '.'], capture_output=True) -radon_report = json.loads(radon_report.stdout) +radon_executable = shutil.which('radon') +radon_command = [ + radon_executable, + 'raw', + '--json', + '.' +] + +radon_report = subprocess.check_output(radon_command).decode('utf-8') # nosec +radon_report = json.loads(radon_report) python_files = [] for _ in radon_report: @@ -20,7 +29,7 @@ @pytest.mark.parametrize( - ["python_file"], + ['python_file'], python_files, ) @scenario('../features/lines_of_code.feature', 'Check Lines of Code in Radon Report') From 02c0770a69dae28b2cd8eb5f829d9b69835a046c Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 19:03:52 +0100 Subject: [PATCH 09/15] fix: dev: Reduce the size of __init__.py to 717 lines of code. --- testinfra_bdd/__init__.py | 50 ------------------------------- testinfra_bdd/given.py | 53 +++++++++++++++++++++++++++++++++ testinfra_bdd/plugins.py | 23 ++++++++++++++ tests/step_defs/test_example.py | 4 ++- 4 files changed, 79 insertions(+), 51 deletions(-) create mode 100644 testinfra_bdd/given.py create mode 100644 testinfra_bdd/plugins.py diff --git a/testinfra_bdd/__init__.py b/testinfra_bdd/__init__.py index bd99f66..f4df56a 100644 --- a/testinfra_bdd/__init__.py +++ b/testinfra_bdd/__init__.py @@ -12,7 +12,6 @@ import testinfra_bdd.pip from pytest_bdd import ( - given, when, then, parsers @@ -26,55 +25,6 @@ __version__ = '1.0.6' -@given(parsers.parse('the host with URL "{hostspec}" is ready'), target_fixture='testinfra_bdd_host') -def the_host_is_ready(hostspec): - """ - Ensure that the host is ready within the specified number of seconds. - - If the host does not become ready within the specified number of seconds, - fail the tests. - - Parameters - ---------- - hostspec : str - The URL of the System Under Test (SUT). Must comply to the Testinfra - URL patterns. See - https://testinfra.readthedocs.io/en/latest/backends.html - - Returns - ------- - testinfra_bdd.fixture.TestinfraBDD - The object to return as a fixture. - """ - return testinfra_bdd.fixture.get_host_fixture(hostspec) - - -@given(parsers.parse('the host with URL "{hostspec}" is ready within {seconds:d} seconds'), - target_fixture='testinfra_bdd_host') -def the_host_is_ready_with_a_number_of_seconds(hostspec, seconds): - """ - Ensure that the host is ready within the specified number of seconds. - - If the host does not become ready within the specified number of seconds, - fail the tests. - - Parameters - ---------- - hostspec : str - The URL of the System Under Test (SUT). Must comply to the Testinfra - URL patterns. See - https://testinfra.readthedocs.io/en/latest/backends.html - seconds : int - The number of seconds that the host is expected to become ready in. - - Returns - ------- - testinfra_bdd.fixture.TestinfraBDD - The object to return as a fixture. - """ - return testinfra_bdd.fixture.get_host_fixture(hostspec, seconds) - - @when(parsers.parse('the {resource_type} is {resource_name}')) @when(parsers.parse('the {resource_type} is "{resource_name}"')) def the_resource_type_is(resource_type, resource_name, testinfra_bdd_host): diff --git a/testinfra_bdd/given.py b/testinfra_bdd/given.py new file mode 100644 index 0000000..2e72742 --- /dev/null +++ b/testinfra_bdd/given.py @@ -0,0 +1,53 @@ +"""The given steps of testinfra-bdd.""" +import testinfra_bdd +from pytest_bdd import given +from pytest_bdd import parsers + + +@given(parsers.parse('the host with URL "{hostspec}" is ready'), target_fixture='testinfra_bdd_host') +def the_host_is_ready(hostspec): + """ + Ensure that the host is ready within the specified number of seconds. + + If the host does not become ready within the specified number of seconds, + fail the tests. + + Parameters + ---------- + hostspec : str + The URL of the System Under Test (SUT). Must comply to the Testinfra + URL patterns. See + https://testinfra.readthedocs.io/en/latest/backends.html + + Returns + ------- + testinfra_bdd.fixture.TestinfraBDD + The object to return as a fixture. + """ + return testinfra_bdd.fixture.get_host_fixture(hostspec) + + +@given(parsers.parse('the host with URL "{hostspec}" is ready within {seconds:d} seconds'), + target_fixture='testinfra_bdd_host') +def the_host_is_ready_with_a_number_of_seconds(hostspec, seconds): + """ + Ensure that the host is ready within the specified number of seconds. + + If the host does not become ready within the specified number of seconds, + fail the tests. + + Parameters + ---------- + hostspec : str + The URL of the System Under Test (SUT). Must comply to the Testinfra + URL patterns. See + https://testinfra.readthedocs.io/en/latest/backends.html + seconds : int + The number of seconds that the host is expected to become ready in. + + Returns + ------- + testinfra_bdd.fixture.TestinfraBDD + The object to return as a fixture. + """ + return testinfra_bdd.fixture.get_host_fixture(hostspec, seconds) diff --git a/testinfra_bdd/plugins.py b/testinfra_bdd/plugins.py new file mode 100644 index 0000000..76b96ca --- /dev/null +++ b/testinfra_bdd/plugins.py @@ -0,0 +1,23 @@ +"""Configure pytest_plugins.""" +global pytest_plugins + + +def add_plugins(): + """ + Add missing plugins to pytest_plugins. + + Returns + ------- + list + The value to set pytest_plugins to. + """ + if 'pytest_plugins' in globals(): + plugins = globals() + else: + plugins = [] + + for plugin in ['testinfra_bdd.given']: + if plugin not in plugins: + plugins.append(plugin) + + return plugins diff --git a/tests/step_defs/test_example.py b/tests/step_defs/test_example.py index efadd16..0840ccf 100644 --- a/tests/step_defs/test_example.py +++ b/tests/step_defs/test_example.py @@ -5,12 +5,14 @@ from pytest_bdd import given from pytest_bdd import scenarios +import testinfra_bdd.plugins + scenarios('../features/example.feature') # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. -pytest_plugins = ['testinfra_bdd'] +pytest_plugins = testinfra_bdd.plugins.add_plugins() @given('on GitHub Actions we skip tests') From 0788ff68b4f87cb0a51fe235afad4a42d756df08 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 21:31:31 +0100 Subject: [PATCH 10/15] fix: dev: Major refactor of code. --- .github/workflows/ci.yml | 7 +- testinfra_bdd/__init__.py | 730 ++------------------------------ testinfra_bdd/file.py | 98 ----- testinfra_bdd/fixture.py | 31 -- testinfra_bdd/given.py | 6 +- testinfra_bdd/pip.py | 84 ---- testinfra_bdd/plugins.py | 23 - testinfra_bdd/then/__init__.py | 1 + testinfra_bdd/then/address.py | 62 +++ testinfra_bdd/then/command.py | 124 ++++++ testinfra_bdd/then/file.py | 198 +++++++++ testinfra_bdd/then/group.py | 59 +++ testinfra_bdd/then/package.py | 42 ++ testinfra_bdd/then/pip.py | 159 +++++++ testinfra_bdd/then/process.py | 31 ++ testinfra_bdd/then/service.py | 82 ++++ testinfra_bdd/then/socket.py | 34 ++ testinfra_bdd/then/user.py | 65 +++ testinfra_bdd/when.py | 40 ++ tests/step_defs/test_example.py | 6 +- tests/step_defs/test_issue21.py | 3 +- tests/test_exceptions.py | 18 +- 22 files changed, 956 insertions(+), 947 deletions(-) delete mode 100644 testinfra_bdd/file.py delete mode 100644 testinfra_bdd/pip.py delete mode 100644 testinfra_bdd/plugins.py create mode 100644 testinfra_bdd/then/__init__.py create mode 100644 testinfra_bdd/then/address.py create mode 100644 testinfra_bdd/then/command.py create mode 100644 testinfra_bdd/then/file.py create mode 100644 testinfra_bdd/then/group.py create mode 100644 testinfra_bdd/then/package.py create mode 100644 testinfra_bdd/then/pip.py create mode 100644 testinfra_bdd/then/process.py create mode 100644 testinfra_bdd/then/service.py create mode 100644 testinfra_bdd/then/socket.py create mode 100644 testinfra_bdd/then/user.py create mode 100644 testinfra_bdd/when.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c9455e..bd3c9a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,8 @@ jobs: pip freeze pip check - - name: Bandit - run: bandit -r . - - - name: Test - run: make test + - name: Run Make + run: make - name: Publish Code Coverage uses: paambaati/codeclimate-action@v3.0.0 diff --git a/testinfra_bdd/__init__.py b/testinfra_bdd/__init__.py index f4df56a..91708e7 100644 --- a/testinfra_bdd/__init__.py +++ b/testinfra_bdd/__init__.py @@ -4,714 +4,62 @@ For documentation and examples, please go to https://github.com/locp/testinfra-bdd """ -import re -import pytest +from testinfra_bdd.fixture import TestinfraBDD -import testinfra_bdd.file -import testinfra_bdd.fixture -import testinfra_bdd.pip +"""PYTEST_MODULES. -from pytest_bdd import ( - when, - then, - parsers -) +A list of all testinfra-bdd packages that contain fixtures. +""" +PYTEST_MODULES = [ + 'testinfra_bdd', + 'testinfra_bdd.given', + 'testinfra_bdd.then.address', + 'testinfra_bdd.then.command', + 'testinfra_bdd.then.file', + 'testinfra_bdd.then.group', + 'testinfra_bdd.then.package', + 'testinfra_bdd.then.pip', + 'testinfra_bdd.then.process', + 'testinfra_bdd.then.service', + 'testinfra_bdd.then.socket', + 'testinfra_bdd.then.user', + 'testinfra_bdd.when' +] """The version of the module. This is used by setuptools and by gitchangelog to identify the name of the name of the release. """ -__version__ = '1.0.6' - - -@when(parsers.parse('the {resource_type} is {resource_name}')) -@when(parsers.parse('the {resource_type} is "{resource_name}"')) -def the_resource_type_is(resource_type, resource_name, testinfra_bdd_host): - """ - Get a resource of a specified type from the system under test. - - Parameters - ---------- - resource_type : str - The type of the resource. - resource_name : str - The name of the resource. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - """ - testinfra_bdd_host.get_resource_from_host(resource_type, resource_name) - - -@when(parsers.parse('the system property {property_name} is not "{expected_value}" skip tests')) -@when(parsers.parse('the system property {property_name} is not {expected_value} skip tests')) -def skip_tests_if_system_info_does_not_match(property_name, expected_value, testinfra_bdd_host): - """ - Skip tests if a system property does not patch the expected value. - - Parameters - ---------- - property_name : str - expected_value : str - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - """ - actual_value = testinfra_bdd_host.get_host_property(property_name) - if actual_value != expected_value: - pytest.skip(f'System {property_name} is {actual_value} which is not {expected_value}.') - - -############################################################################# -# Command checks. -############################################################################# -@then(parsers.parse('the command {command} exists in path')) -@then(parsers.parse('the command "{command}" exists in path')) -def check_command_exists_in_path(command, testinfra_bdd_host): - """ - Assert that a specified command is present on the host path. - - Parameters - ---------- - command : str - The name of the command to check for. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the command is not found on the path. - """ - message = f'Unable to find the command "{command}" on the path.' - assert testinfra_bdd_host.host.exists(command), message - - -@then(parsers.parse('the command {stream_name} contains "{text}"')) -def check_command_stream_contains(stream_name, text, testinfra_bdd_host): - """ - Check that the stdout or stderr stream contains a string. - - Parameters - ---------- - stream_name : str - The name of the stream to check. Must be "stdout" or "stderr". - text : str - The text to search for. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the specified stream does not contain the expected text. - """ - stream = testinfra_bdd_host.get_stream_from_command(stream_name) - message = f'The string "{text}" was not found in the {stream_name} ("{stream}") of the command.' - assert text in stream, message +__version__ = '2.0.0' -@then(parsers.parse('the command {stream_name} contains the regex "{pattern}"')) -def check_command_stream_contains_the_regex(stream_name, pattern, testinfra_bdd_host): +def get_host_fixture(hostspec, timeout=0): """ - Check that the stdout or stderr stream matches a regular expression pattern. + Return a host that is confirmed as ready. - Parameters - ---------- - stream_name : str - The name of the stream to be checked. Must be stdout or stderr. - pattern : str - The pattern to search for in the stream. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. + hostspec : str + The URL of the System Under Test (SUT). Must comply to the Testinfra + URL patterns. See + https://testinfra.readthedocs.io/en/latest/backends.html + timeout : int, optional + The number of seconds that the host is expected to become ready in. - Raises - ------ - AssertError - When the specified stream does not match the pattern. - ValueError - When the stream name is not recognized. - """ - stream = testinfra_bdd_host.get_stream_from_command(stream_name) - message = f'The regex "{pattern}" is not found in the {stream_name} "{stream}".' - # The parsers.parse function escapes the parsed string. We need to clean it up before using it. - pattern = pattern.encode('utf-8').decode('unicode_escape') - prog = re.compile(pattern) - assert prog.search(stream) is not None, message - - -@then(parsers.parse('the command return code is {expected_return_code:d}')) -def check_command_return_code(expected_return_code, testinfra_bdd_host): - """ - Check that the expected return code from a command matches the actual return code. - - Parameters - ---------- - expected_return_code : int - The expected return code (e.g. zero/0). - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. + Returns + ------- + testinfra_bdd.fixture.TestinfraBDD + The object to return as a fixture. Raises ------ AssertError - When the actual return code does not match the expected return code. - """ - cmd = testinfra_bdd_host.command - actual_return_code = cmd.rc - message = f'Expected a return code of {expected_return_code} but got {actual_return_code}.' - assert expected_return_code == actual_return_code, message - - -@then(parsers.parse('the command {stream_name} is empty')) -def command_stream_is_empty(stream_name, testinfra_bdd_host): - """ - Check that the specified command stream is empty. - - Parameters - ---------- - stream_name : str - The name of the stream to be checked. Must be stdout or stderr. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the specified stream does not match the pattern. - """ - stream = testinfra_bdd_host.get_stream_from_command(stream_name) - assert not stream, f'Expected {stream_name} to be empty ("{stream}").' - - -############################################################################# -# Service checks. -############################################################################# -@then('the service is not enabled') -def the_service_is_not_enabled(testinfra_bdd_host): - """ - Check that the service is not enabled. - - Parameters - ---------- - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the service is enabled. - """ - service = testinfra_bdd_host.service - message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be disabled, but it is enabled.' - assert not service.is_enabled, message - - -@then('the service is enabled') -def the_service_is_enabled(testinfra_bdd_host): + When the host is not ready. """ - Check that the service is enabled. - - Parameters - ---------- - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the service is not enabled. - """ - service = testinfra_bdd_host.service - message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be enabled, but it is disabled.' - assert service.is_enabled, message - - -@then('the service is not running') -def the_service_is_not_running(testinfra_bdd_host): - """ - Check that the service is not running. - - Parameters - ---------- - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the service is running. - """ - service = testinfra_bdd_host.service - message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to not be running.' - assert not service.is_running, message - - -@then('the service is running') -def the_service_is_running(testinfra_bdd_host): - """ - Check that the service is running. - - Parameters - ---------- - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the service is not running. - """ - service = testinfra_bdd_host.service - message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be running.' - assert service.is_running, message - - -############################################################################# -# Pip package checks. -############################################################################# -@then('the pip check is OK') -def the_pip_check_is_ok(testinfra_bdd_host): - """ - Verify installed packages have compatible dependencies. - - Parameters - ---------- - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the packages have incompatible dependencies. - """ - host = testinfra_bdd_host.host - cmd = host.pip.check() - message = f'Incompatible Pip packages - {cmd.stdout} {cmd.stderr}' - assert cmd.rc == 0, message - - -@then(parsers.parse('the pip package state is {expected_state}')) -@then(parsers.parse('the pip package is {expected_state}')) -def the_pip_package_state_is(expected_state, testinfra_bdd_host): - """ - Check the state of a Pip package. - - Parameters - ---------- - expected_state : str - The expected state of the package. Can be absent, latest or installed. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the actual state doesn't match the expected state. - """ - (actual_state, message) = testinfra_bdd.pip.get_pip_package_actual_state( - testinfra_bdd_host.pip_package, - expected_state, - testinfra_bdd_host.host - ) - assert actual_state == expected_state, message - - -@then(parsers.parse('the pip package version is {expected_version}')) -def the_pip_package_version_is(expected_version, testinfra_bdd_host): - """ - Check the version of a Pip package. - - Parameters - ---------- - expected_version : str - The version of the package that is expected. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the actual version is not the expected version. - """ - pip_package = testinfra_bdd_host.pip_package - assert pip_package, 'Pip package not set. Have you missed a "When pip package is" step?' - actual_version = pip_package.version - message = f'Expected Pip package version to be {expected_version} but it was {actual_version}.' - assert actual_version == expected_version, message - - -############################################################################# -# System package checks. -############################################################################# -@then(parsers.parse('the package state is {expected_status}')) -@then(parsers.parse('the package is {expected_status}')) -def the_package_status_is(expected_status, testinfra_bdd_host): - """ - Check the status of a package (installed/absent). - - Parameters - ---------- - expected_status : str - Can be absent, installed or present. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the package is not in the expected state. - """ - status_lookup = { - 'absent': False, - 'installed': True, - 'present': True - } - expected_to_be_installed = status_lookup[expected_status] - pkg = testinfra_bdd_host.package - actual_status = pkg.is_installed - - if expected_to_be_installed: - message = f'Expected {pkg.name} to be {expected_status} on {testinfra_bdd_host.hostname} but it is absent.' - - if actual_status: - message = f'Expected {pkg.name} to be absent on {testinfra_bdd_host.hostname} ' - message += 'but it is installed ({pkg.version}).' - - assert actual_status == expected_to_be_installed, message - - -############################################################################# -# File checks. -############################################################################# -@then(parsers.parse('the file contents contains "{text}"')) -def the_file_contents_contains_text(text, testinfra_bdd_host): - """ - Check if the file contains a string. - - Parameters - ---------- - text : str - The string to search for in the file content. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the file does not contain the string. - """ - file = testinfra_bdd_host.file - assert file.contains(text), f'The file {testinfra_bdd_host.hostname}:{file.path} does not contain "{text}".' - - -@then(parsers.parse('the file contents contains the regex "{pattern}"')) -def the_file_contents_matches_the_regex(pattern, testinfra_bdd_host): - """ - Check if the file contains matches a regex pattern. - - Parameters - ---------- - pattern : str - The regular expression to match against the file content. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the regex does not match the file content. - """ - file = testinfra_bdd_host.file - file_name = f'{testinfra_bdd_host.hostname}:{file.path}' - message = f'The regex "{pattern}" does not match the content of {file_name} ("{file.content_string}").' - # The parsers.parse function escapes the parsed string. We need to clean it up before using it. - pattern = pattern.encode('utf-8').decode('unicode_escape') - assert re.search(pattern, file.content_string) is not None, message - - -@then(parsers.parse('the file is {expected_status}')) -def the_file_status(expected_status, testinfra_bdd_host): - """ - Check if the file is present or absent. - - Parameters - ---------- - expected_status : str - Should be present or absent. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - When the expected status does not match the actual status. - """ - the_file_property_is('state', expected_status, testinfra_bdd_host) - - -@then(parsers.parse('the file {property_name} is {expected_value}')) -def the_file_property_is(property_name, expected_value, testinfra_bdd_host): - """ - Check the property of a file. - - Parameters - ---------- - property_name : str - The name of the property to compare. - expected_value : str - The value that is expected. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual value does not match the expected value. - """ - (actual_value, exception_message) = testinfra_bdd.file.get_file_actual_state( - testinfra_bdd_host.file, - property_name, - expected_value - ) - assert actual_value == expected_value, exception_message - - -############################################################################# -# User checks. -############################################################################# -@then(parsers.parse('the user {property_name} is {expected_value}')) -def the_user_property_is(property_name, expected_value, testinfra_bdd_host): - """ - Check the property of a user. - - Parameters - ---------- - property_name : str - The name of the property to compare. - expected_value : str - The value that is expected. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual value does not match the expected value. - """ - user = testinfra_bdd_host.user - assert user, 'User not set. Have you missed a "When user is" step?' - - if testinfra_bdd_host.user.exists: - actual_state = 'present' - properties = { - 'gid': str(user.gid), - 'group': user.group, - 'home': user.home, - 'shell': user.shell, - 'state': actual_state, - 'uid': str(user.uid) - } + if timeout: + message = f'The host {hostspec} is not ready within {timeout} seconds.' else: - actual_state = 'absent' - properties = { - 'state': actual_state - } - - assert property_name in properties, f'Unknown user property "{property_name}".' - actual_value = properties[property_name] - message = f'Expected {property_name} for user {user.name} to be "{expected_value}" ' - message += f'but it was "{actual_value}".' - assert actual_value == expected_value, message - - -@then(parsers.parse('the user is {expected_state}')) -def check_the_user_state(expected_state, testinfra_bdd_host): - """ - Check that the actual state of a user matches the expected state. - - Parameters - ---------- - expected_state : str - The expected state (e.g. absent or present). - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - """ - the_user_property_is('state', expected_state, testinfra_bdd_host) - - -############################################################################# -# Group checks. -############################################################################# -@then(parsers.parse('the group {property_name} is {expected_value}')) -def the_group_property_is(property_name, expected_value, testinfra_bdd_host): - """ - Check the property of a group. - - Parameters - ---------- - property_name : str - The name of the property to compare. - expected_value : str - The value that is expected. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual value does not match the expected value. - """ - group = testinfra_bdd_host.group - assert group, 'Group not set. Have you missed a "When group is" step?' - - properties = { - 'gid': None, - 'state': 'absent' - } - - if group.exists: - properties = { - 'gid': str(group.gid), - 'state': 'present' - } - - assert property_name in properties, f'Unknown group property ({property_name}).' - actual_value = properties[property_name] - message = f'Expected group property to be {expected_value} but it was {actual_value}.' - assert actual_value == expected_value, message - - -@then(parsers.parse('the group is {expected_state}')) -def check_the_group_state(expected_state, testinfra_bdd_host): - """ - Check that the actual state of a group matches the expected state. - - Parameters - ---------- - expected_state : str - The expected state (e.g. absent or present). - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - """ - the_group_property_is('state', expected_state, testinfra_bdd_host) - - -############################################################################# -# Process checks. -############################################################################# -@then(parsers.parse('the process count is {expected_count:d}')) -def the_process_count_is(expected_count, testinfra_bdd_host): - """ - Check that the process count matches the expected count. - - Parameters - ---------- - expected_count : int - The expected number of processes. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual process count does not match the expected count. - """ - specification = testinfra_bdd_host.process_specification - processes = testinfra_bdd_host.processes - assert processes, 'No process set, did you forget a "When process filter" step?' - actual_process_count = len(processes) - message = f'Expected process specification "{specification}" to return {expected_count} ' - message += f'but found {actual_process_count} "{processes}".' - assert actual_process_count == expected_count, message - - -############################################################################# -# Process checks. -############################################################################# -@then(parsers.parse('the address is {expected_state}')) -def the_address_is(expected_state, testinfra_bdd_host): - """ - Check the actual state of an address against an expected state. - - Parameters - ---------- - expected_state : str - The expected state of the address. - - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual state does not match the state. - """ - address = testinfra_bdd_host.address - assert address, 'Address is not set. Did you miss a "When address is" step?' - properties = { - 'resolvable': address.is_resolvable, - 'reachable': address.is_reachable - } - assert expected_state in properties, f'Invalid state for {address.name} ("{expected_state}").' - message = f'Expected the address {address.name} to be {expected_state} but it is not.' - assert properties[expected_state], message - - -@then(parsers.parse('the port is {expected_state}')) -def the_port_is(expected_state, testinfra_bdd_host): - """ - Check the actual state of an address port against an expected state. - - Parameters - ---------- - expected_state : str - The expected state of the port. - - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual state does not match the state. - """ - port = testinfra_bdd_host.port - assert port, 'Port is not set. Did you miss a "When the address and port" step?' - properties = { - 'reachable': port.is_reachable - } - assert expected_state in properties, f'Unknown Port property ("{expected_state}").' - message = f'{testinfra_bdd_host.address.name}:{testinfra_bdd_host.port_number} is unreachable.' - assert properties['reachable'], message - - -############################################################################# -# Socket checks. -############################################################################# -@then(parsers.parse('the socket is {expected_state}')) -def the_socket_is(expected_state, testinfra_bdd_host): - """ - Check the state of a socket. - - Parameters - ---------- - expected_state : str - The expected state of the socket. - testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD - The test fixture. - - Raises - ------ - AssertError - If the actual state does not match the state. - """ - socket = testinfra_bdd_host.socket - socket_url = testinfra_bdd_host.socket_url - actual_state = 'not listening' - assert socket, 'Socket is not set. Have you missed a "When socket is" step?' - - if socket.is_listening: - actual_state = 'listening' + message = f'The host {hostspec} is not ready.' - message = f'Expected socket {socket_url} to be {expected_state} but it is {actual_state}.' - assert actual_state == expected_state, message + host = TestinfraBDD(hostspec) + assert host.is_host_ready(timeout), message + return host diff --git a/testinfra_bdd/file.py b/testinfra_bdd/file.py deleted file mode 100644 index 458a2ea..0000000 --- a/testinfra_bdd/file.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Functions required for files.""" -from testinfra_bdd.exception_message import exception_message - - -def get_file_actual_state(file, property_name, expected_state): - """ - Get the actual state of a file given the package and the expected state. - - Parameters - ---------- - file : testinfra.File - The file to be checked. - property_name : str - The name of the property to check (e.g. state). - expected_state : str - The expected state. - - Returns - ------- - tuple - str - The actual state (e.g. absent, latest, present or superseded). - str - A suitable message if the actual state doesn't match the actual state. - """ - properties = get_file_properties(file) - assert property_name in properties, f'Unknown user property "{property_name}".' - actual_state = properties[property_name] - return actual_state, exception_message(f'File {file.path} {property_name}', actual_state, expected_state) - - -def get_file_properties(file): - """ - Get the properties of the file. - - Parameters - ---------- - file : testinfra.File - The file to be checked. - - Returns - ------- - dict - A dictionary of the properties. - """ - assert file, 'File not set. Have you missed a "When file is" step?' - properties = { - 'group': None, - 'mode': None, - 'owner': None, - 'state': 'absent', - 'type': None, - 'user': None - } - - if file.exists: - properties = { - 'group': file.group, - 'mode': '0o%o' % file.mode, - 'owner': file.user, - 'state': 'present', - 'type': get_file_type(file), - 'user': file.user - } - - return properties - - -def get_file_type(file): - """ - Get the file type. - - Parameters - ---------- - file : testinfra.File - The file to be checked. - - Returns - ------- - str - The type of file. - """ - file_type = None - - type_lookup = { - 'file': file.is_file, - 'directory': file.is_directory, - 'pipe': file.is_pipe, - 'socket': file.is_socket, - 'symlink': file.is_symlink - } - - for key in type_lookup: - if type_lookup[key]: - file_type = key - break - - return file_type diff --git a/testinfra_bdd/fixture.py b/testinfra_bdd/fixture.py index 2e3caf7..0cb7ebe 100644 --- a/testinfra_bdd/fixture.py +++ b/testinfra_bdd/fixture.py @@ -238,34 +238,3 @@ def wait_until_is_host_ready(self, timeout=0): now = time.time() return is_ready - - -def get_host_fixture(hostspec, timeout=0): - """ - Return a host that is confirmed as ready. - - hostspec : str - The URL of the System Under Test (SUT). Must comply to the Testinfra - URL patterns. See - https://testinfra.readthedocs.io/en/latest/backends.html - timeout : int, optional - The number of seconds that the host is expected to become ready in. - - Returns - ------- - testinfra_bdd.fixture.TestinfraBDD - The object to return as a fixture. - - Raises - ------ - AssertError - When the host is not ready. - """ - if timeout: - message = f'The host {hostspec} is not ready within {timeout} seconds.' - else: - message = f'The host {hostspec} is not ready.' - - host = TestinfraBDD(hostspec) - assert host.is_host_ready(timeout), message - return host diff --git a/testinfra_bdd/given.py b/testinfra_bdd/given.py index 2e72742..ca94f8f 100644 --- a/testinfra_bdd/given.py +++ b/testinfra_bdd/given.py @@ -1,5 +1,5 @@ """The given steps of testinfra-bdd.""" -import testinfra_bdd +import testinfra_bdd.fixture from pytest_bdd import given from pytest_bdd import parsers @@ -24,7 +24,7 @@ def the_host_is_ready(hostspec): testinfra_bdd.fixture.TestinfraBDD The object to return as a fixture. """ - return testinfra_bdd.fixture.get_host_fixture(hostspec) + return testinfra_bdd.get_host_fixture(hostspec) @given(parsers.parse('the host with URL "{hostspec}" is ready within {seconds:d} seconds'), @@ -50,4 +50,4 @@ def the_host_is_ready_with_a_number_of_seconds(hostspec, seconds): testinfra_bdd.fixture.TestinfraBDD The object to return as a fixture. """ - return testinfra_bdd.fixture.get_host_fixture(hostspec, seconds) + return testinfra_bdd.get_host_fixture(hostspec, seconds) diff --git a/testinfra_bdd/pip.py b/testinfra_bdd/pip.py deleted file mode 100644 index c74ed5d..0000000 --- a/testinfra_bdd/pip.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Functions required for Pip packages.""" - -from testinfra_bdd.exception_message import exception_message - - -def check_entry_requirements(pip_package, expected_state): - """ - Check that the entry requirements are met for the test. - - Parameters - ---------- - pip_package : testinfra.Pip - The Pip package to be checked. - expected_state : str - The expected state. - - Raises - ------ - ValueError - If the expected state is invalid. - RuntimeError - If the Pip package has not been set. - """ - valid_expected_states = [ - 'absent', - 'latest', - 'present', - 'superseded' - ] - - if expected_state not in valid_expected_states: - raise ValueError(f'Unknown expected state "{expected_state}" for a Pip package.') - elif not pip_package: - raise RuntimeError('Pip package not set. Have you missed a "When pip package is" step?') - - -def get_pip_package_actual_state(pip_package, expected_state, host): - """ - Get the actual state of a Pip package given the package and the expected state. - - Parameters - ---------- - pip_package : testinfra.Pip - The Pip package to be checked. - expected_state : str - The expected state. - host : testinfra.host.Host - The host to be checked against. - - Returns - ------- - tuple - str - The actual state (e.g. absent, latest, present or superseded). - str - A suitable message if the actual state doesn't match the actual state. - """ - state_checks = [ - 'absent', - 'present' - ] - - check_entry_requirements(pip_package, expected_state) - - if expected_state in state_checks: - actual_state = 'absent' - - if pip_package.is_installed: - actual_state = 'present' - - return actual_state, exception_message( - f'Pip package {pip_package.name}', - actual_state, - expected_state - ) - - outdated_packages = host.pip.get_outdated_packages() - - if pip_package.name in outdated_packages: - actual_state = 'superseded' - else: - actual_state = 'latest' - - return actual_state, exception_message(f'Pip package {pip_package.name}', actual_state, expected_state) diff --git a/testinfra_bdd/plugins.py b/testinfra_bdd/plugins.py deleted file mode 100644 index 76b96ca..0000000 --- a/testinfra_bdd/plugins.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Configure pytest_plugins.""" -global pytest_plugins - - -def add_plugins(): - """ - Add missing plugins to pytest_plugins. - - Returns - ------- - list - The value to set pytest_plugins to. - """ - if 'pytest_plugins' in globals(): - plugins = globals() - else: - plugins = [] - - for plugin in ['testinfra_bdd.given']: - if plugin not in plugins: - plugins.append(plugin) - - return plugins diff --git a/testinfra_bdd/then/__init__.py b/testinfra_bdd/then/__init__.py new file mode 100644 index 0000000..43fa053 --- /dev/null +++ b/testinfra_bdd/then/__init__.py @@ -0,0 +1 @@ +"""The then packages/fixtures for testinfra-bdd.""" diff --git a/testinfra_bdd/then/address.py b/testinfra_bdd/then/address.py new file mode 100644 index 0000000..200d92e --- /dev/null +++ b/testinfra_bdd/then/address.py @@ -0,0 +1,62 @@ +"""Then address fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the address is {expected_state}')) +def the_address_is(expected_state, testinfra_bdd_host): + """ + Check the actual state of an address against an expected state. + + Parameters + ---------- + expected_state : str + The expected state of the address. + + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual state does not match the state. + """ + address = testinfra_bdd_host.address + assert address, 'Address is not set. Did you miss a "When address is" step?' + properties = { + 'resolvable': address.is_resolvable, + 'reachable': address.is_reachable + } + assert expected_state in properties, f'Invalid state for {address.name} ("{expected_state}").' + message = f'Expected the address {address.name} to be {expected_state} but it is not.' + assert properties[expected_state], message + + +@then(parsers.parse('the port is {expected_state}')) +def the_port_is(expected_state, testinfra_bdd_host): + """ + Check the actual state of an address port against an expected state. + + Parameters + ---------- + expected_state : str + The expected state of the port. + + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual state does not match the state. + """ + port = testinfra_bdd_host.port + assert port, 'Port is not set. Did you miss a "When the address and port" step?' + properties = { + 'reachable': port.is_reachable + } + assert expected_state in properties, f'Unknown Port property ("{expected_state}").' + message = f'{testinfra_bdd_host.address.name}:{testinfra_bdd_host.port_number} is unreachable.' + assert properties['reachable'], message diff --git a/testinfra_bdd/then/command.py b/testinfra_bdd/then/command.py new file mode 100644 index 0000000..f33add3 --- /dev/null +++ b/testinfra_bdd/then/command.py @@ -0,0 +1,124 @@ +"""Then command fixtures for testinfra-bdd.""" +import re + +from pytest_bdd import parsers +from pytest_bdd import then + + +@then(parsers.parse('the command {command} exists in path')) +@then(parsers.parse('the command "{command}" exists in path')) +def check_command_exists_in_path(command, testinfra_bdd_host): + """ + Assert that a specified command is present on the host path. + + Parameters + ---------- + command : str + The name of the command to check for. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the command is not found on the path. + """ + message = f'Unable to find the command "{command}" on the path.' + assert testinfra_bdd_host.host.exists(command), message + + +@then(parsers.parse('the command {stream_name} contains "{text}"')) +def check_command_stream_contains(stream_name, text, testinfra_bdd_host): + """ + Check that the stdout or stderr stream contains a string. + + Parameters + ---------- + stream_name : str + The name of the stream to check. Must be "stdout" or "stderr". + text : str + The text to search for. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the specified stream does not contain the expected text. + """ + stream = testinfra_bdd_host.get_stream_from_command(stream_name) + message = f'The string "{text}" was not found in the {stream_name} ("{stream}") of the command.' + assert text in stream, message + + +@then(parsers.parse('the command {stream_name} contains the regex "{pattern}"')) +def check_command_stream_contains_the_regex(stream_name, pattern, testinfra_bdd_host): + """ + Check that the stdout or stderr stream matches a regular expression pattern. + + Parameters + ---------- + stream_name : str + The name of the stream to be checked. Must be stdout or stderr. + pattern : str + The pattern to search for in the stream. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the specified stream does not match the pattern. + ValueError + When the stream name is not recognized. + """ + stream = testinfra_bdd_host.get_stream_from_command(stream_name) + message = f'The regex "{pattern}" is not found in the {stream_name} "{stream}".' + # The parsers.parse function escapes the parsed string. We need to clean it up before using it. + pattern = pattern.encode('utf-8').decode('unicode_escape') + prog = re.compile(pattern) + assert prog.search(stream) is not None, message + + +@then(parsers.parse('the command return code is {expected_return_code:d}')) +def check_command_return_code(expected_return_code, testinfra_bdd_host): + """ + Check that the expected return code from a command matches the actual return code. + + Parameters + ---------- + expected_return_code : int + The expected return code (e.g. zero/0). + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the actual return code does not match the expected return code. + """ + cmd = testinfra_bdd_host.command + actual_return_code = cmd.rc + message = f'Expected a return code of {expected_return_code} but got {actual_return_code}.' + assert expected_return_code == actual_return_code, message + + +@then(parsers.parse('the command {stream_name} is empty')) +def command_stream_is_empty(stream_name, testinfra_bdd_host): + """ + Check that the specified command stream is empty. + + Parameters + ---------- + stream_name : str + The name of the stream to be checked. Must be stdout or stderr. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the specified stream does not match the pattern. + """ + stream = testinfra_bdd_host.get_stream_from_command(stream_name) + assert not stream, f'Expected {stream_name} to be empty ("{stream}").' diff --git a/testinfra_bdd/then/file.py b/testinfra_bdd/then/file.py new file mode 100644 index 0000000..393027d --- /dev/null +++ b/testinfra_bdd/then/file.py @@ -0,0 +1,198 @@ +"""Then file fixtures for testinfra-bdd.""" +import re + +from pytest_bdd import ( + then, + parsers +) + +from testinfra_bdd.exception_message import exception_message + + +@then(parsers.parse('the file contents contains "{text}"')) +def the_file_contents_contains_text(text, testinfra_bdd_host): + """ + Check if the file contains a string. + + Parameters + ---------- + text : str + The string to search for in the file content. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the file does not contain the string. + """ + file = testinfra_bdd_host.file + assert file.contains(text), f'The file {testinfra_bdd_host.hostname}:{file.path} does not contain "{text}".' + + +@then(parsers.parse('the file contents contains the regex "{pattern}"')) +def the_file_contents_matches_the_regex(pattern, testinfra_bdd_host): + """ + Check if the file contains matches a regex pattern. + + Parameters + ---------- + pattern : str + The regular expression to match against the file content. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the regex does not match the file content. + """ + file = testinfra_bdd_host.file + file_name = f'{testinfra_bdd_host.hostname}:{file.path}' + message = f'The regex "{pattern}" does not match the content of {file_name} ("{file.content_string}").' + # The parsers.parse function escapes the parsed string. We need to clean it up before using it. + pattern = pattern.encode('utf-8').decode('unicode_escape') + assert re.search(pattern, file.content_string) is not None, message + + +@then(parsers.parse('the file is {expected_status}')) +def the_file_status(expected_status, testinfra_bdd_host): + """ + Check if the file is present or absent. + + Parameters + ---------- + expected_status : str + Should be present or absent. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the expected status does not match the actual status. + """ + the_file_property_is('state', expected_status, testinfra_bdd_host) + + +@then(parsers.parse('the file {property_name} is {expected_value}')) +def the_file_property_is(property_name, expected_value, testinfra_bdd_host): + """ + Check the property of a file. + + Parameters + ---------- + property_name : str + The name of the property to compare. + expected_value : str + The value that is expected. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual value does not match the expected value. + """ + (actual_value, exception_message) = get_file_actual_state( + testinfra_bdd_host.file, + property_name, + expected_value + ) + assert actual_value == expected_value, exception_message + + +def get_file_actual_state(file, property_name, expected_state): + """ + Get the actual state of a file given the package and the expected state. + + Parameters + ---------- + file : testinfra.File + The file to be checked. + property_name : str + The name of the property to check (e.g. state). + expected_state : str + The expected state. + + Returns + ------- + tuple + str + The actual state (e.g. absent, latest, present or superseded). + str + A suitable message if the actual state doesn't match the actual state. + """ + properties = get_file_properties(file) + assert property_name in properties, f'Unknown user property "{property_name}".' + actual_state = properties[property_name] + return actual_state, exception_message(f'File {file.path} {property_name}', actual_state, expected_state) + + +def get_file_properties(file): + """ + Get the properties of the file. + + Parameters + ---------- + file : testinfra.File + The file to be checked. + + Returns + ------- + dict + A dictionary of the properties. + """ + assert file, 'File not set. Have you missed a "When file is" step?' + properties = { + 'group': None, + 'mode': None, + 'owner': None, + 'state': 'absent', + 'type': None, + 'user': None + } + + if file.exists: + properties = { + 'group': file.group, + 'mode': '0o%o' % file.mode, + 'owner': file.user, + 'state': 'present', + 'type': get_file_type(file), + 'user': file.user + } + + return properties + + +def get_file_type(file): + """ + Get the file type. + + Parameters + ---------- + file : testinfra.File + The file to be checked. + + Returns + ------- + str + The type of file. + """ + file_type = None + + type_lookup = { + 'file': file.is_file, + 'directory': file.is_directory, + 'pipe': file.is_pipe, + 'socket': file.is_socket, + 'symlink': file.is_symlink + } + + for key in type_lookup: + if type_lookup[key]: + file_type = key + break + + return file_type diff --git a/testinfra_bdd/then/group.py b/testinfra_bdd/then/group.py new file mode 100644 index 0000000..bd4ebd7 --- /dev/null +++ b/testinfra_bdd/then/group.py @@ -0,0 +1,59 @@ +"""Then file fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the group {property_name} is {expected_value}')) +def the_group_property_is(property_name, expected_value, testinfra_bdd_host): + """ + Check the property of a group. + + Parameters + ---------- + property_name : str + The name of the property to compare. + expected_value : str + The value that is expected. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual value does not match the expected value. + """ + group = testinfra_bdd_host.group + assert group, 'Group not set. Have you missed a "When group is" step?' + + properties = { + 'gid': None, + 'state': 'absent' + } + + if group.exists: + properties = { + 'gid': str(group.gid), + 'state': 'present' + } + + assert property_name in properties, f'Unknown group property ({property_name}).' + actual_value = properties[property_name] + message = f'Expected group property to be {expected_value} but it was {actual_value}.' + assert actual_value == expected_value, message + + +@then(parsers.parse('the group is {expected_state}')) +def check_the_group_state(expected_state, testinfra_bdd_host): + """ + Check that the actual state of a group matches the expected state. + + Parameters + ---------- + expected_state : str + The expected state (e.g. absent or present). + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + """ + the_group_property_is('state', expected_state, testinfra_bdd_host) diff --git a/testinfra_bdd/then/package.py b/testinfra_bdd/then/package.py new file mode 100644 index 0000000..5a71c9c --- /dev/null +++ b/testinfra_bdd/then/package.py @@ -0,0 +1,42 @@ +"""Then system package fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the package state is {expected_status}')) +@then(parsers.parse('the package is {expected_status}')) +def the_package_status_is(expected_status, testinfra_bdd_host): + """ + Check the status of a package (installed/absent). + + Parameters + ---------- + expected_status : str + Can be absent, installed or present. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the package is not in the expected state. + """ + status_lookup = { + 'absent': False, + 'installed': True, + 'present': True + } + expected_to_be_installed = status_lookup[expected_status] + pkg = testinfra_bdd_host.package + actual_status = pkg.is_installed + + if expected_to_be_installed: + message = f'Expected {pkg.name} to be {expected_status} on {testinfra_bdd_host.hostname} but it is absent.' + + if actual_status: + message = f'Expected {pkg.name} to be absent on {testinfra_bdd_host.hostname} ' + message += 'but it is installed ({pkg.version}).' + + assert actual_status == expected_to_be_installed, message diff --git a/testinfra_bdd/then/pip.py b/testinfra_bdd/then/pip.py new file mode 100644 index 0000000..449e55b --- /dev/null +++ b/testinfra_bdd/then/pip.py @@ -0,0 +1,159 @@ +"""Then pip package fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + +from testinfra_bdd.exception_message import exception_message + + +def check_entry_requirements(pip_package, expected_state): + """ + Check that the entry requirements are met for the test. + + Parameters + ---------- + pip_package : testinfra.Pip + The Pip package to be checked. + expected_state : str + The expected state. + + Raises + ------ + ValueError + If the expected state is invalid. + RuntimeError + If the Pip package has not been set. + """ + valid_expected_states = [ + 'absent', + 'latest', + 'present', + 'superseded' + ] + + if expected_state not in valid_expected_states: + raise ValueError(f'Unknown expected state "{expected_state}" for a Pip package.') + elif not pip_package: + raise RuntimeError('Pip package not set. Have you missed a "When pip package is" step?') + + +def get_pip_package_actual_state(pip_package, expected_state, host): + """ + Get the actual state of a Pip package given the package and the expected state. + + Parameters + ---------- + pip_package : testinfra.Pip + The Pip package to be checked. + expected_state : str + The expected state. + host : testinfra.host.Host + The host to be checked against. + + Returns + ------- + tuple + str + The actual state (e.g. absent, latest, present or superseded). + str + A suitable message if the actual state doesn't match the actual state. + """ + state_checks = [ + 'absent', + 'present' + ] + + check_entry_requirements(pip_package, expected_state) + + if expected_state in state_checks: + actual_state = 'absent' + + if pip_package.is_installed: + actual_state = 'present' + + return actual_state, exception_message( + f'Pip package {pip_package.name}', + actual_state, + expected_state + ) + + outdated_packages = host.pip.get_outdated_packages() + + if pip_package.name in outdated_packages: + actual_state = 'superseded' + else: + actual_state = 'latest' + + return actual_state, exception_message(f'Pip package {pip_package.name}', actual_state, expected_state) + + +@then('the pip check is OK') +def the_pip_check_is_ok(testinfra_bdd_host): + """ + Verify installed packages have compatible dependencies. + + Parameters + ---------- + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the packages have incompatible dependencies. + """ + host = testinfra_bdd_host.host + cmd = host.pip.check() + message = f'Incompatible Pip packages - {cmd.stdout} {cmd.stderr}' + assert cmd.rc == 0, message + + +@then(parsers.parse('the pip package state is {expected_state}')) +@then(parsers.parse('the pip package is {expected_state}')) +def the_pip_package_state_is(expected_state, testinfra_bdd_host): + """ + Check the state of a Pip package. + + Parameters + ---------- + expected_state : str + The expected state of the package. Can be absent, latest or installed. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the actual state doesn't match the expected state. + """ + (actual_state, message) = get_pip_package_actual_state( + testinfra_bdd_host.pip_package, + expected_state, + testinfra_bdd_host.host + ) + assert actual_state == expected_state, message + + +@then(parsers.parse('the pip package version is {expected_version}')) +def the_pip_package_version_is(expected_version, testinfra_bdd_host): + """ + Check the version of a Pip package. + + Parameters + ---------- + expected_version : str + The version of the package that is expected. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the actual version is not the expected version. + """ + pip_package = testinfra_bdd_host.pip_package + assert pip_package, 'Pip package not set. Have you missed a "When pip package is" step?' + actual_version = pip_package.version + message = f'Expected Pip package version to be {expected_version} but it was {actual_version}.' + assert actual_version == expected_version, message diff --git a/testinfra_bdd/then/process.py b/testinfra_bdd/then/process.py new file mode 100644 index 0000000..c72fd2c --- /dev/null +++ b/testinfra_bdd/then/process.py @@ -0,0 +1,31 @@ +"""Then process fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the process count is {expected_count:d}')) +def the_process_count_is(expected_count, testinfra_bdd_host): + """ + Check that the process count matches the expected count. + + Parameters + ---------- + expected_count : int + The expected number of processes. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual process count does not match the expected count. + """ + specification = testinfra_bdd_host.process_specification + processes = testinfra_bdd_host.processes + assert processes, 'No process set, did you forget a "When process filter" step?' + actual_process_count = len(processes) + message = f'Expected process specification "{specification}" to return {expected_count} ' + message += f'but found {actual_process_count} "{processes}".' + assert actual_process_count == expected_count, message diff --git a/testinfra_bdd/then/service.py b/testinfra_bdd/then/service.py new file mode 100644 index 0000000..552e5be --- /dev/null +++ b/testinfra_bdd/then/service.py @@ -0,0 +1,82 @@ +"""Then service fixtures for testinfra-bdd.""" +from pytest_bdd import then + + +@then('the service is not enabled') +def the_service_is_not_enabled(testinfra_bdd_host): + """ + Check that the service is not enabled. + + Parameters + ---------- + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the service is enabled. + """ + service = testinfra_bdd_host.service + message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be disabled, but it is enabled.' + assert not service.is_enabled, message + + +@then('the service is enabled') +def the_service_is_enabled(testinfra_bdd_host): + """ + Check that the service is enabled. + + Parameters + ---------- + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the service is not enabled. + """ + service = testinfra_bdd_host.service + message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be enabled, but it is disabled.' + assert service.is_enabled, message + + +@then('the service is not running') +def the_service_is_not_running(testinfra_bdd_host): + """ + Check that the service is not running. + + Parameters + ---------- + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the service is running. + """ + service = testinfra_bdd_host.service + message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to not be running.' + assert not service.is_running, message + + +@then('the service is running') +def the_service_is_running(testinfra_bdd_host): + """ + Check that the service is running. + + Parameters + ---------- + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + When the service is not running. + """ + service = testinfra_bdd_host.service + message = f'Expected {service.name} on host {testinfra_bdd_host.hostname} to be running.' + assert service.is_running, message diff --git a/testinfra_bdd/then/socket.py b/testinfra_bdd/then/socket.py new file mode 100644 index 0000000..ea2e9d1 --- /dev/null +++ b/testinfra_bdd/then/socket.py @@ -0,0 +1,34 @@ +"""Then socket fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the socket is {expected_state}')) +def the_socket_is(expected_state, testinfra_bdd_host): + """ + Check the state of a socket. + + Parameters + ---------- + expected_state : str + The expected state of the socket. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual state does not match the state. + """ + socket = testinfra_bdd_host.socket + socket_url = testinfra_bdd_host.socket_url + actual_state = 'not listening' + assert socket, 'Socket is not set. Have you missed a "When socket is" step?' + + if socket.is_listening: + actual_state = 'listening' + + message = f'Expected socket {socket_url} to be {expected_state} but it is {actual_state}.' + assert actual_state == expected_state, message diff --git a/testinfra_bdd/then/user.py b/testinfra_bdd/then/user.py new file mode 100644 index 0000000..aff84e9 --- /dev/null +++ b/testinfra_bdd/then/user.py @@ -0,0 +1,65 @@ +"""Then user fixtures for testinfra-bdd.""" +from pytest_bdd import ( + then, + parsers +) + + +@then(parsers.parse('the user {property_name} is {expected_value}')) +def the_user_property_is(property_name, expected_value, testinfra_bdd_host): + """ + Check the property of a user. + + Parameters + ---------- + property_name : str + The name of the property to compare. + expected_value : str + The value that is expected. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + + Raises + ------ + AssertError + If the actual value does not match the expected value. + """ + user = testinfra_bdd_host.user + assert user, 'User not set. Have you missed a "When user is" step?' + + if testinfra_bdd_host.user.exists: + actual_state = 'present' + properties = { + 'gid': str(user.gid), + 'group': user.group, + 'home': user.home, + 'shell': user.shell, + 'state': actual_state, + 'uid': str(user.uid) + } + else: + actual_state = 'absent' + properties = { + 'state': actual_state + } + + assert property_name in properties, f'Unknown user property "{property_name}".' + actual_value = properties[property_name] + message = f'Expected {property_name} for user {user.name} to be "{expected_value}" ' + message += f'but it was "{actual_value}".' + assert actual_value == expected_value, message + + +@then(parsers.parse('the user is {expected_state}')) +def check_the_user_state(expected_state, testinfra_bdd_host): + """ + Check that the actual state of a user matches the expected state. + + Parameters + ---------- + expected_state : str + The expected state (e.g. absent or present). + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + """ + the_user_property_is('state', expected_state, testinfra_bdd_host) diff --git a/testinfra_bdd/when.py b/testinfra_bdd/when.py new file mode 100644 index 0000000..af19b69 --- /dev/null +++ b/testinfra_bdd/when.py @@ -0,0 +1,40 @@ +"""The when steps of testinfra-bdd.""" +import pytest +from pytest_bdd import parsers +from pytest_bdd import when + + +@when(parsers.parse('the {resource_type} is {resource_name}')) +@when(parsers.parse('the {resource_type} is "{resource_name}"')) +def the_resource_type_is(resource_type, resource_name, testinfra_bdd_host): + """ + Get a resource of a specified type from the system under test. + + Parameters + ---------- + resource_type : str + The type of the resource. + resource_name : str + The name of the resource. + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + """ + testinfra_bdd_host.get_resource_from_host(resource_type, resource_name) + + +@when(parsers.parse('the system property {property_name} is not "{expected_value}" skip tests')) +@when(parsers.parse('the system property {property_name} is not {expected_value} skip tests')) +def skip_tests_if_system_info_does_not_match(property_name, expected_value, testinfra_bdd_host): + """ + Skip tests if a system property does not patch the expected value. + + Parameters + ---------- + property_name : str + expected_value : str + testinfra_bdd_host : testinfra_bdd.fixture.TestinfraBDD + The test fixture. + """ + actual_value = testinfra_bdd_host.get_host_property(property_name) + if actual_value != expected_value: + pytest.skip(f'System {property_name} is {actual_value} which is not {expected_value}.') diff --git a/tests/step_defs/test_example.py b/tests/step_defs/test_example.py index 0840ccf..a94d269 100644 --- a/tests/step_defs/test_example.py +++ b/tests/step_defs/test_example.py @@ -2,17 +2,17 @@ import os import pytest +import testinfra_bdd + from pytest_bdd import given from pytest_bdd import scenarios -import testinfra_bdd.plugins - scenarios('../features/example.feature') # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. -pytest_plugins = testinfra_bdd.plugins.add_plugins() +pytest_plugins = testinfra_bdd.PYTEST_MODULES @given('on GitHub Actions we skip tests') diff --git a/tests/step_defs/test_issue21.py b/tests/step_defs/test_issue21.py index 402a173..a364eb5 100644 --- a/tests/step_defs/test_issue21.py +++ b/tests/step_defs/test_issue21.py @@ -1,4 +1,5 @@ """Fix Issue 21 feature tests.""" +import testinfra_bdd from pytest_bdd import scenarios @@ -6,4 +7,4 @@ # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. -pytest_plugins = ['testinfra_bdd'] +pytest_plugins = testinfra_bdd.PYTEST_MODULES diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 796fc41..46ff645 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,7 +1,9 @@ """Test exceptions are raised as expected.""" import pytest import testinfra_bdd -import testinfra_bdd.fixture + +from testinfra_bdd.then.pip import the_pip_package_state_is +from testinfra_bdd import get_host_fixture def test_invalid_resource_type(): @@ -9,7 +11,7 @@ def test_invalid_resource_type(): exception_raised = False try: - host = testinfra_bdd.fixture.get_host_fixture('docker://sut') + host = testinfra_bdd.get_host_fixture('docker://sut') host.get_resource_from_host('foo', 'foo') except ValueError as ex: exception_raised = True @@ -23,7 +25,7 @@ def test_invalid_command_stream_name(): exception_raised = False try: - host = testinfra_bdd.fixture.get_host_fixture('docker://sut') + host = get_host_fixture('docker://sut') host.get_resource_from_host('command', 'ls') host.get_stream_from_command('foo') except ValueError as ex: @@ -38,7 +40,7 @@ def test_unready_host(): exception_raised = False try: - testinfra_bdd.fixture.get_host_fixture('docker://foo', 1) + get_host_fixture('docker://foo', 1) except AssertionError as ex: exception_raised = True assert str(ex) == 'The host docker://foo is not ready within 1 seconds.' @@ -57,13 +59,13 @@ def test_unready_host(): def test_pip_package(pip, expected_state, expected_exception_message): """Test that a superseded pip package is identified.""" exception_raised = False - host = testinfra_bdd.fixture.get_host_fixture('docker://sut') + host = get_host_fixture('docker://sut') if pip: host.get_resource_from_host('pip package', pip) try: - testinfra_bdd.the_pip_package_state_is(expected_state, host) + the_pip_package_state_is(expected_state, host) except (AssertionError, RuntimeError, ValueError) as ex: exception_raised = True assert str(ex) == expected_exception_message @@ -84,7 +86,7 @@ def test_invalid_process_specifications(process_specification): expected_message = f'Unable to parse process filters "{process_specification}".' try: - host = testinfra_bdd.fixture.get_host_fixture('docker://sut') + host = get_host_fixture('docker://sut') host.get_resource_from_host('process filter', process_specification) except ValueError as ex: exception_raised = True @@ -110,7 +112,7 @@ def test_invalid_addr_and_port_specifications(specification): } try: - host = testinfra_bdd.fixture.get_host_fixture('docker://sut') + host = get_host_fixture('docker://sut') host.get_resource_from_host('address and port', specification) except ValueError as ex: exception_raised = True From 0e1c245a9ca8155e5d09980bf432982882b00b67 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 21:46:50 +0100 Subject: [PATCH 11/15] fix: dev: Make imports consistent. --- .codeclimate.yml | 2 -- tests/test_exceptions.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index a6beeba..76004c5 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -44,8 +44,6 @@ checks: threshold: # language-specific defaults. overrides affect all languages. plugins: - bandit: - enabled: true markdownlint: enabled: true radon: diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 46ff645..ae64467 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,6 +1,5 @@ """Test exceptions are raised as expected.""" import pytest -import testinfra_bdd from testinfra_bdd.then.pip import the_pip_package_state_is from testinfra_bdd import get_host_fixture @@ -11,7 +10,7 @@ def test_invalid_resource_type(): exception_raised = False try: - host = testinfra_bdd.get_host_fixture('docker://sut') + host = get_host_fixture('docker://sut') host.get_resource_from_host('foo', 'foo') except ValueError as ex: exception_raised = True From 4db4c5e9a53fa8cbb5b3e3d3610a895ed5f59e97 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 22:24:58 +0100 Subject: [PATCH 12/15] fix: dev: Set minimum test coverage. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 93d4d43..2cb9718 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [coverage:report] -fail_under = 90 +fail_under = 95.77 show_missing = True [coverage:run] From 8a9a3d386af5ffbe3ac8a0eccf21c6e3e33e4f44 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 22:25:54 +0100 Subject: [PATCH 13/15] new: Add "When" step to allow skipping of tests depending on environment variable. --- testinfra_bdd/when.py | 17 +++++++++++++++++ tests/features/example.feature | 12 ++++++++---- tests/step_defs/test_example.py | 12 ------------ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/testinfra_bdd/when.py b/testinfra_bdd/when.py index af19b69..1545304 100644 --- a/testinfra_bdd/when.py +++ b/testinfra_bdd/when.py @@ -1,4 +1,5 @@ """The when steps of testinfra-bdd.""" +import os import pytest from pytest_bdd import parsers from pytest_bdd import when @@ -38,3 +39,19 @@ def skip_tests_if_system_info_does_not_match(property_name, expected_value, test actual_value = testinfra_bdd_host.get_host_property(property_name) if actual_value != expected_value: pytest.skip(f'System {property_name} is {actual_value} which is not {expected_value}.') + + +@when(parsers.parse('the environment variable {key} is {value} skip tests')) +def skip_tests_if_env_key_is(key, value): + """ + Skip tests if an environment variable is set to a particular value. + + Parameters + ---------- + key : str + The name of the environment variable. + value : str + The value the environment variable must be for the tests to be skipped. + """ + if key in os.environ and os.environ[key] == value: + pytest.skip(f'Environment variable {key} is set to {value}.') diff --git a/tests/features/example.feature b/tests/features/example.feature index d5900c3..b98bdb2 100644 --- a/tests/features/example.feature +++ b/tests/features/example.feature @@ -128,17 +128,21 @@ Feature: Example of Testinfra BDD | udp://123 | listening | | tcp://22 | not listening | + Scenario: Skip Tests Due to Environment Variable + Given the host with URL "docker://java11" is ready + When the environment variable PYTHONPATH is .:.. skip tests + Scenario: Check Network Address Given the host with URL "docker://sut" is ready within 10 seconds - And on GitHub Actions we skip tests - When the address is www.google.com + When the environment variable GITHUB_ACTIONS is true skip tests + And the address is www.google.com Then the address is resolvable And the address is reachable Scenario: Check Network Address With Port Given the host with URL "docker://sut" is ready within 10 seconds - And on GitHub Actions we skip tests - When the address and port is www.google.com:443 + When the environment variable GITHUB_ACTIONS is true skip tests + And the address and port is www.google.com:443 Then the address is resolvable And the address is reachable And the port is reachable diff --git a/tests/step_defs/test_example.py b/tests/step_defs/test_example.py index a94d269..beb8d08 100644 --- a/tests/step_defs/test_example.py +++ b/tests/step_defs/test_example.py @@ -1,10 +1,5 @@ """Examples of step definitions for Testinfra BDD feature tests.""" -import os -import pytest - import testinfra_bdd - -from pytest_bdd import given from pytest_bdd import scenarios scenarios('../features/example.feature') @@ -13,10 +8,3 @@ # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. pytest_plugins = testinfra_bdd.PYTEST_MODULES - - -@given('on GitHub Actions we skip tests') -def on_github_actions_we_skip_tests(): - """on GitHub Actions we skip tests.""" - if 'GITHUB_ACTIONS' in os.environ and os.environ['GITHUB_ACTIONS'] == 'true': - pytest.skip('GitHub Actions does not support Ping/ICMP.') From ef17ac71ec3788e348e4efb37b3a844aa7b4822b Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 22:47:40 +0100 Subject: [PATCH 14/15] chg: doc: Add details on upgrading from 1.X.X to 2.0.0. --- README.md | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2ade140..8fedfba 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ The file `tests/features/example.feature` could look something like: Feature: Example of Testinfra BDD Give an example of all the possible Given, When and Then steps. + The Given steps to skip the address and port tests when running under + GitHub actions are not part of the testinfra-bdd package itself, but are + required as GitHub/Azure does not allow Ping/ICMP traffic. + Scenario: Skip Tests if Host is Windoze Given the host with URL "docker://sut" is ready within 10 seconds # The system property can be one of: @@ -112,7 +116,7 @@ Feature: Example of Testinfra BDD When the pip package is testinfra-bdd # Can check if the package is absent or present. Then the pip package is present - And the pip package version is 0.3.0 + And the pip package version is 1.0.6 # Check that installed packages have compatible dependencies. And the pip check is OK @@ -155,15 +159,21 @@ Feature: Example of Testinfra BDD | udp://123 | listening | | tcp://22 | not listening | + Scenario: Skip Tests Due to Environment Variable + Given the host with URL "docker://java11" is ready + When the environment variable PYTHONPATH is .:.. skip tests + Scenario: Check Network Address Given the host with URL "docker://sut" is ready within 10 seconds - When the address is www.google.com + When the environment variable GITHUB_ACTIONS is true skip tests + And the address is www.google.com Then the address is resolvable And the address is reachable Scenario: Check Network Address With Port Given the host with URL "docker://sut" is ready within 10 seconds - When the address and port is www.google.com:443 + When the environment variable GITHUB_ACTIONS is true skip tests + And the address and port is www.google.com:443 Then the address is resolvable And the address is reachable And the port is reachable @@ -187,6 +197,7 @@ and `tests/step_defs/test_example.py` contains the following: ```python """Examples of step definitions for Testinfra BDD feature tests.""" +import testinfra_bdd from pytest_bdd import scenarios scenarios('../features/example.feature') @@ -194,7 +205,7 @@ scenarios('../features/example.feature') # Ensure that the PyTest fixtures provided in testinfra-bdd are available to # your test suite. -pytest_plugins = ['testinfra_bdd'] +pytest_plugins = testinfra_bdd.PYTEST_MODULES ``` ## "Given" Steps @@ -280,3 +291,21 @@ Example: Given the host with URL "docker://sut" is ready within 10 seconds When the system property type is not Windoze skip tests ``` + +## Upgrading from 1.Y.Z to 2.0.0 + +We split the single package into multiple source files. This means a minor +but nonetheless breaking change in your step definitions (all feature files +can remain as they are). The change is how one sets `pytest_plugins`. + +### Old Code + +```python +pytest_plugins = ['testinfra_bdd'] +``` + +### New Code + +```python +pytest_plugins = testinfra_bdd.PYTEST_MODULES +``` From 06fc32bbb2e66073fb9483294b3a5a75798a8657 Mon Sep 17 00:00:00 2001 From: Ben Dalling Date: Thu, 21 Jul 2022 22:48:17 +0100 Subject: [PATCH 15/15] chg: doc: Release 2.0.0 !minor --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 995a663..532f419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,24 @@ # Changelog -## 1.0.6 +## 2.0.0 + +### New + +* Add "When" step to allow skipping of tests depending on environment variable. [Ben Dalling] + +### Changes + +* Add details on upgrading from 1.X.X to 2.0.0. [Ben Dalling] + +* Add the Snyk badge and reduce the size of the step functions. [Ben Dalling] + +### Fix + +* Refactor Markdown. [Ben Dalling] + + +## 1.0.6 (2022-07-18) ### Fix