diff --git a/.darglint b/.darglint new file mode 100644 index 0000000000..1f11b17206 --- /dev/null +++ b/.darglint @@ -0,0 +1,4 @@ +[darglint] +docstring_style=sphinx +enable=DAR104 +strictness=long diff --git a/.flake8 b/.flake8 index 3f08837737..58c95e4039 100644 --- a/.flake8 +++ b/.flake8 @@ -1,15 +1,9 @@ [flake8] # Don't even try to analyze these: -exclude = - # No need to traverse egg files - *.egg, +extend-exclude = # No need to traverse egg info dir *.egg-info, - # No need to traverse eggs directory - .eggs, - # No need to traverse our git directory - .git, # GitHub configs .github, # Cache files of MyPy @@ -18,14 +12,10 @@ exclude = .pytest_cache, # Temp dir of pytest-testmon .tmontmp, - # Countless third-party libs in venvs - .tox, # Occasional virtualenv dir .venv # VS Code .vscode, - # There's no value in checking cache directories - __pycache__, # Temporary build dir build, # This contains sdists and wheels of ansible-lint that we don't want to check @@ -46,75 +36,67 @@ max-line-length = 100 # The only allowed ignores are related to black and isort # https://black.readthedocs.io/en/stable/the_black_code_style.html#line-length # "H" are generated by hacking plugin, which is not black compatible -ignore = E203,E501,W503,H +extend-ignore = + E203, + E501, + H, # Allow certain violations in certain files: per-file-ignores = # FIXME: D100 Missing docstring in public module # FIXME: D101 Missing docstring in public class # FIXME: D102 Missing docstring in public method - # FIXME: D103 Missing docstring in public function # FIXME: drop these once they're made simpler # Ref: https://github.com/ansible-community/ansible-lint/issues/744 - src/ansiblelint/cli.py: D101 D102 D103 + src/ansiblelint/cli.py: D101 D102 src/ansiblelint/formatters/__init__.py: D101 D102 - src/ansiblelint/utils.py: D103 src/ansiblelint/rules/*.py: D100 D101 D102 src/ansiblelint/rules/__init__.py: D100 D101 D102 # FIXME: drop these once they're fixed # Ref: https://github.com/ansible-community/ansible-lint/issues/725 test/__init__.py: D102 - test/conftest.py: D100 D103 + test/conftest.py: D100 test/rules/EMatcherRule.py: D100 D101 D102 test/rules/UnsetVariableMatcherRule.py: D100 D101 D102 - test/TestAnsibleLintRule.py: D100 D103 - test/TestBaseFormatter.py: D100 D103 - test/TestBecomeUserWithoutBecome.py: PT009 D100 D101 D102 - test/TestCliRolePaths.py: PT009 D100 D101 D102 - test/TestCommandLineInvocationSameAsConfig.py: D100 D103 - test/TestCommandHasChangesCheck.py: PT009 D100 D101 D102 - test/TestComparisonToEmptyString.py: PT009 D100 D101 D102 - test/TestComparisonToLiteralBool.py: PT009 D100 D101 D102 - test/TestDependenciesInMeta.py: D100 D103 - test/TestDeprecatedModule.py: PT009 D100 D101 D102 - test/TestEnvVarsInCommand.py: PT009 D100 D101 D102 + test/TestAnsibleLintRule.py: D100 + test/TestBaseFormatter.py: D100 + test/TestBecomeUserWithoutBecome.py: D100 D101 D102 + test/TestCliRolePaths.py: D100 D101 D102 + test/TestCommandLineInvocationSameAsConfig.py: D100 + test/TestCommandHasChangesCheck.py: D100 D101 D102 + test/TestComparisonToLiteralBool.py: D100 D101 D102 + test/TestDependenciesInMeta.py: D100 + test/TestDeprecatedModule.py: D100 D101 D102 + test/TestEnvVarsInCommand.py: D100 D101 D102 test/TestFormatter.py: D100 D101 D102 - test/TestImportIncludeRole.py: D100 D103 - test/TestImportWithMalformed.py: D100 D103 - test/TestIncludeMissingFileRule.py: D100 D103 - test/TestIncludeMissFileWithRole.py: D100 D103 - test/TestLineNumber.py: D100 - test/TestLineTooLong.py: PT009 D100 D101 D102 - test/TestLintRule.py: PT009 D100 D101 D102 - test/TestNestedJinjaRule.py: D100 D103 + test/TestImportIncludeRole.py: D100 + test/TestImportWithMalformed.py: D100 + test/TestIncludeMissFileWithRole.py: D100 + test/TestLineTooLong.py: D100 D101 D102 + test/TestLintRule.py: D100 D101 D102 + test/TestNestedJinjaRule.py: D100 test/TestMatchError.py: D101 - test/TestMetaChangeFromDefault.py: PT009 D100 D101 D102 - test/TestMetaMainHasInfo.py: PT009 D100 D101 D102 - test/TestMetaTagValid.py: PT009 D100 D101 D102 - test/TestMetaVideoLinks.py: PT009 D100 D101 D102 - test/TestNoFormattingInWhenRule.py: PT009 D100 D101 D102 - test/TestNoLogPasswordsRule.py: PT009 D100 D101 D102 - test/TestOctalPermissions.py: PT009 D100 D101 D102 - test/TestPackageIsNotLatest.py: PT009 D100 D101 D102 - test/TestPretaskIncludePlaybook.py: D100 D103 - test/TestRoleHandlers.py: PT009 D100 D101 D102 - test/TestRoleRelativePath.py: PT009 D100 D101 D102 - test/TestRuleProperties.py: D100 D103 - test/TestRulesCollection.py: D100 D103 - test/TestRunner.py: D100 D103 - test/TestShellWithoutPipefail.py: PT009 D100 D101 D102 - test/TestSkipImportPlaybook.py: D100 D103 - test/TestSkipInsideYaml.py: D100 D103 - test/TestSkipPlaybookItems.py: D100 D103 - test/TestTaskHasName.py: PT009 D100 D101 D102 - test/TestTaskIncludes.py: D100 D103 - test/TestTaskNoLocalAction.py: PT009 D100 D101 D102 - test/TestUseCommandInsteadOfShell.py: PT009 D100 D101 D102 - test/TestUseHandlerRatherThanWhenChanged.py: PT009 D100 D101 D102 - test/TestUsingBareVariablesIsDeprecated.py: PT009 D100 D101 D102 - test/TestVariableHasSpaces.py: PT009 D100 D101 D102 - test/TestWithSkipTagId.py: PT009 D100 D101 D102 + test/TestMetaChangeFromDefault.py: D100 D101 D102 + test/TestMetaMainHasInfo.py: D100 D101 D102 + test/TestMetaVideoLinks.py: D100 D101 D102 + test/TestNoFormattingInWhenRule.py: D100 D101 D102 + test/TestOctalPermissions.py: D100 D101 D102 + test/TestPackageIsNotLatest.py: D100 D101 D102 + test/TestRoleRelativePath.py: D100 D101 D102 + test/TestRuleProperties.py: D100 + test/TestRulesCollection.py: D100 + test/TestRunner.py: D100 + test/TestShellWithoutPipefail.py: D100 D101 D102 + test/TestSkipImportPlaybook.py: D100 + test/TestSkipInsideYaml.py: D100 + test/TestSkipPlaybookItems.py: D100 + test/TestTaskHasName.py: D100 D101 D102 + test/TestTaskIncludes.py: D100 + test/TestTaskNoLocalAction.py: D100 D101 D102 + test/TestUseHandlerRatherThanWhenChanged.py: D100 D101 D102 + test/TestUsingBareVariablesIsDeprecated.py: D100 D101 D102 + test/TestWithSkipTagId.py: D100 D101 D102 # flake8-pytest-style # PT001: diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index d0cfca8f8f..4b812494cb 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -41,10 +41,10 @@ Automated tests will be run against all PRs for flake8 compliance and Ansible compatibility — to check before pushing commits, just use `tox`_. -.. _.flake8: https://github.com/ansible-community/ansible-lint/blob/master/.flake8 +.. _.flake8: https://github.com/ansible-community/ansible-lint/blob/main/.flake8 .. _supported Ansible versions: - https://docs.ansible.com/ansible/devel/reference_appendices - /release_and_maintenance.html#release-status + https://docs.ansible.com/ansible-core/devel/reference_appendices + /release_and_maintenance.html#ansible-core-release-cycle .. _tox: https://tox.readthedocs.io .. DO-NOT-REMOVE-deps-snippet-PLACEHOLDER diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2d2497d0a3..91a1052b4d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,13 +1,13 @@ --- name: Bug report about: > - Create a bug report. Please test against the master branch before + Create a bug report. Please test against the main branch before submitting it. For anything else, please use discussions link below. labels: bug, new --- - + ##### Summary diff --git a/.github/SECURITY.rst b/.github/SECURITY.rst index b9190d843c..15a39766a7 100644 --- a/.github/SECURITY.rst +++ b/.github/SECURITY.rst @@ -5,8 +5,12 @@ Supported Versions ================== Ansible applies security fixes according to the 3-versions-back support -policy. Please find more information in `our docs -`_. +policy. Please find more information in `our docs`_. + +.. _our docs: + https://docs.ansible.com/ansible-core/devel/reference_appendices + /release_and_maintenance.html#ansible-core-release-cycle + Reporting a Vulnerability ========================= diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 97d09eaff6..a40633f396 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -4,7 +4,7 @@ on: push: # branches to consider in the event; optional, defaults to all branches: - - master + - main - 'releases/**' - 'stable/**' @@ -12,7 +12,7 @@ jobs: update_release_draft: runs-on: ubuntu-20.04 steps: - # Drafts your next Release notes as Pull Requests are merged into "master" + # Drafts your next Release notes as Pull Requests are merged into "main" - uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index fcccf1d960..8b9cf7579b 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -6,7 +6,7 @@ on: - "**" push: # only publishes pushes to the main branch to TestPyPI branches: # any integration branch but not tag - - "master" + - "main" pull_request: release: types: @@ -32,6 +32,7 @@ jobs: env: - TOXENV: lint - TOXENV: docs + - TOXENV: linkcheck-docs - TOXENV: eco - TOXENV: packaging env: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01ea812cea..47ef7c8336 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,32 +18,6 @@ repos: language: system files: >- ^setup\.py$ - - id: pip-compile - name: pip-compile - entry: pip-compile -q --no-annotate --output-file=test-requirements.txt setup.cfg test-requirements.in - language: system - files: ^(test-requirements\.(txt|in)|setup\.cfg)$ - pass_filenames: false - - id: pip-compile-docs - name: pip-compile-docs - entry: pip-compile -q --no-annotate --output-file=docs/requirements.txt docs/requirements.in setup.cfg - language: system - files: ^(docs\/requirements\.(txt|in)|setup\.cfg)$ - pass_filenames: false - - id: pip-compile-upgrade - name: pip-compile-upgrade - entry: pip-compile -q --upgrade --no-annotate --output-file=test-requirements.txt setup.cfg test-requirements.in - language: system - files: ^(test-requirements\.(txt|in)|setup\.cfg)$ - pass_filenames: false - stages: [manual] - - id: pip-compile-docs-upgrade - name: pip-compile-docs-upgrade - entry: pip-compile -q --upgrade --no-annotate --output-file=docs/requirements.txt docs/requirements.in setup.cfg - language: system - files: ^(docs\/requirements\.(txt|in)|setup\.cfg)$ - pass_filenames: false - stages: [manual] - repo: https://github.com/pre-commit/pre-commit-hooks.git rev: v4.0.1 hooks: @@ -55,7 +29,7 @@ repos: examples/playbooks/example.yml )$ - id: mixed-line-ending - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements @@ -69,7 +43,7 @@ repos: hooks: - id: doc8 - repo: https://github.com/adrienverge/yamllint.git - rev: v1.26.1 + rev: v1.26.2 hooks: - id: yamllint exclude: > @@ -145,3 +119,32 @@ repos: - tenacity - typing_extensions - wcmatch +- # keep at bottom as these are slower + repo: local + hooks: + - id: pip-compile + name: pip-compile + entry: pip-compile -q --no-annotate --output-file=constraints.txt setup.py setup.cfg --extra test --extra yamllint + language: system + files: ^(setup\.cfg|=constraints\.txt)$ + pass_filenames: false + - id: pip-compile-docs + name: pip-compile-docs + entry: pip-compile -q --no-annotate --output-file=docs/requirements.txt --extra yamllint docs/requirements.in setup.cfg + language: system + files: ^(docs\/requirements\.(txt|in)|)$ + pass_filenames: false + - id: pip-compile-upgrade + name: pip-compile-upgrade + entry: pip-compile -q --upgrade --no-annotate --output-file=constraints.txt setup.py setup.cfg --extra test --extra yamllint + language: system + files: ^(setup\.cfg|=constraints\.txt)$ + pass_filenames: false + stages: [manual] + - id: pip-compile-docs-upgrade + name: pip-compile-docs-upgrade + entry: pip-compile -q --upgrade --no-annotate --extra yamllint --output-file=docs/requirements.txt docs/requirements.in setup.cfg + language: system + files: ^(docs\/requirements\.(txt|in)|)$ + pass_filenames: false + stages: [manual] diff --git a/README.rst b/README.rst index b126572757..3e13674484 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ :alt: Discussions .. image:: https://github.com/ansible-community/ansible-lint/workflows/gh/badge.svg - :target: https://github.com/ansible-community/ansible-lint/actions?query=workflow%3Agh+branch%3Amaster+event%3Apush + :target: https://github.com/ansible-community/ansible-lint/actions?query=workflow%3Agh+branch%3Amain+event%3Apush :alt: GitHub Actions CI/CD .. image:: https://img.shields.io/lgtm/grade/python/g/ansible-community/ansible-lint.svg?logo=lgtm&logoWidth=18 @@ -66,5 +66,5 @@ ansible-lint was created by `Will Thames`_ and is now maintained as part of the .. _Will Thames: https://github.com/willthames .. _Ansible: https://ansible.com .. _Red Hat: https://redhat.com -.. _MIT: https://github.com/ansible-community/ansible-lint/blob/master/LICENSE +.. _MIT: https://github.com/ansible-community/ansible-lint/blob/main/LICENSE .. _GPLv3: https://github.com/ansible/ansible/blob/devel/COPYING diff --git a/test-requirements.txt b/constraints.txt similarity index 62% rename from test-requirements.txt rename to constraints.txt index 7d10756fdc..5f69eb2912 100644 --- a/test-requirements.txt +++ b/constraints.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with python 3.9 # To update, run: # -# pip-compile --no-annotate --output-file=test-requirements.txt setup.cfg test-requirements.in +# pip-compile --extra=test --extra=yamllint --no-annotate --output-file=constraints.txt setup.cfg setup.py # attrs==21.2.0 bracex==2.1.1 @@ -14,22 +14,21 @@ execnet==1.9.0 flaky==3.7.0 iniconfig==1.1.1 packaging==21.0 +pathspec==0.9.0 pluggy==0.13.1 psutil==5.8.0 py==1.10.0 -pygments==2.9.0 +pygments==2.10.0 pyparsing==2.4.7 pytest==6.2.4 pytest-cov==2.12.1 pytest-forked==1.3.0 pytest-xdist==2.3.0 pyyaml==5.4.1 -rich==10.6.0 -ruamel.yaml==0.17.10 ; python_version >= "3.7" +rich==10.7.0 +ruamel.yaml==0.17.11 ; python_version >= "3.7" ruamel.yaml.clib==0.2.6 tenacity==8.0.1 toml==0.10.2 wcmatch==8.2 - -# The following packages are considered to be unsafe in a requirements file: -# setuptools +yamllint==1.26.2 diff --git a/docs/conf.py b/docs/conf.py index ada5f06b02..17a46ad0ea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ import os import sys from pathlib import Path +from typing import List import pkg_resources @@ -53,7 +54,10 @@ 'myst_parser', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + # Third-party extensions: + 'sphinxcontrib.apidoc', 'sphinxcontrib.programoutput', + # Tree-local extensions: 'rules_table_generator_ext', # in-tree extension ] @@ -82,6 +86,17 @@ # The master toctree document. master_doc = 'index' +apidoc_excluded_paths: List[str] = [] +apidoc_extra_args = [ + '--implicit-namespaces', + '--private', # include “_private” modules +] +apidoc_module_dir = '../src/ansiblelint' +apidoc_module_first = False +apidoc_output_dir = 'pkg' +apidoc_separate_modules = True +apidoc_toc_file = None + # General substitutions. project = 'Ansible Lint Documentation' copyright = "2013-2021 Ansible, Inc" # pylint: disable=redefined-builtin @@ -99,6 +114,16 @@ "gh": (f"{github_url}/%s", "GitHub: "), } +intersphinx_mapping = { + 'ansible': ('https://docs.ansible.com/ansible/devel/', None), + 'ansible-core': ('https://docs.ansible.com/ansible-core/devel/', None), + 'packaging': ('https://packaging.rtfd.io/en/latest', None), + 'pytest': ('https://docs.pytest.org/en/latest', None), + 'python': ('https://docs.python.org/3', None), + 'python2': ('https://docs.python.org/2', None), + 'rich': ('https://rich.rtfd.io/en/latest', None), +} + # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # @@ -182,7 +207,7 @@ 'display_github': 'True', 'github_user': 'ansible-community', 'github_repo': 'ansible-lint', - 'github_version': 'master/docs/', + 'github_version': 'main/docs/', 'current_version': version, 'latest_version': 'latest', # list specifically out of order to make latest work @@ -299,3 +324,28 @@ 'theme_overrides.css', # override wide tables in RTD theme 'ansi.css', ] + +linkcheck_workers = 25 + +nitpicky = True +nitpick_ignore = [ + ('py:class', 'ansible.parsing.yaml.objects.AnsibleBaseYAMLObject'), + ('py:class', 'Lintable'), + ('py:class', 'yaml'), + ('py:class', 'role'), + ('py:class', 'requirements'), + ('py:class', 'handlers'), + ('py:class', 'tasks'), + ('py:class', 'meta'), + ('py:class', 'playbook'), + ('py:class', 'AnsibleBaseYAMLObject'), + ('py:class', 'Namespace'), + ('py:class', 'RulesCollection'), + ('py:class', '_pytest.fixtures.SubRequest'), + ('py:class', 'MatchError'), + ('py:class', 'Pattern'), + ('py:class', 'odict'), + ('py:class', 'LintResult'), + ('py:obj', 'Any'), + ('py:obj', 'ansiblelint.formatters.T'), +] diff --git a/docs/contributing.rst b/docs/contributing.rst index e57a8ee16a..c96197be6a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -46,5 +46,5 @@ to create a new rule by following the steps below: * Build the docs using :command:`tox -e docs` and check that the new rule is displayed correctly in them. -.. _MetaTagValidRule: https://github.com/ansible-community/ansible-lint/blob/master/src/ansiblelint/rules/MetaTagValidRule.py +.. _MetaTagValidRule: https://github.com/ansible-community/ansible-lint/blob/main/src/ansiblelint/rules/MetaTagValidRule.py .. _rules: https://ansible-lint.readthedocs.io/en/latest/default_rules.html diff --git a/docs/index.rst b/docs/index.rst index f49eacf5c3..48d824b424 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,8 +8,8 @@ About Ansible Lint Ansible Lint is a command-line tool for linting **playbooks, roles and collections** aimed towards any Ansible users. Its main goal is to promote -proven practices, patterns and behaviors while avoiding common pitfals that can -easily lead to bugs or make code harder to maintain. +proven practices, patterns and behaviors while avoiding common pitfalls that +can easily lead to bugs or make code harder to maintain. Ansible lint is also supposed to help users upgrade their code to work with newer versions of Ansible. Due to this reason we recommend using it with @@ -62,3 +62,4 @@ Ansible teams. :caption: Contributing contributing + Private unsupported (dev) API autodoc diff --git a/docs/pkg/.gitignore b/docs/pkg/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/docs/pkg/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/docs/requirements.in b/docs/requirements.in index da94dab9e3..900b00c374 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -1,7 +1,9 @@ ansible-base # needed by rules_table_generator myst-parser pipdeptree +pytest Sphinx sphinx_ansible_theme +sphinxcontrib-apidoc >= 0.3.0 sphinxcontrib-programoutput2>=2.0a1 yamllint diff --git a/docs/requirements.txt b/docs/requirements.txt index f689e64cad..2c3b5e10e1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,45 +2,51 @@ # This file is autogenerated by pip-compile with python 3.9 # To update, run: # -# pip-compile --no-annotate --output-file=docs/requirements.txt docs/requirements.in setup.cfg +# pip-compile --extra=yamllint --no-annotate --output-file=docs/requirements.txt docs/requirements.in setup.cfg # alabaster==0.7.12 -ansible-base==2.10.10 +ansible-base==2.10.13 +ansible-pygments==0.1.0 attrs==21.2.0 babel==2.9.1 bracex==2.1.1 certifi==2021.5.30 -cffi==1.14.5 -chardet==4.0.0 +cffi==1.14.6 +charset-normalizer==2.0.4 colorama==0.4.4 commonmark==0.9.1 cryptography==3.4.7 docutils==0.16 enrich==1.2.6 -idna==2.10 +idna==3.2 imagesize==1.2.0 +iniconfig==1.1.1 jinja2==3.0.1 markdown-it-py==1.1.0 markupsafe==2.0.1 mdit-py-plugins==0.2.8 -myst-parser==0.14.0 -packaging==20.9 -pathspec==0.8.1 -pipdeptree==2.0.0 +myst-parser==0.15.1 +packaging==21.0 +pathspec==0.9.0 +pbr==5.6.0 +pipdeptree==2.1.0 +pluggy==0.13.1 +py==1.10.0 pycparser==2.20 -pygments==2.9.0 +pygments==2.10.0 pyparsing==2.4.7 +pytest==6.2.4 pytz==2021.1 pyyaml==5.4.1 -requests==2.25.1 -rich==10.2.2 -ruamel.yaml==0.17.7 ; python_version >= "3.7" -ruamel.yaml.clib==0.2.2 -six==1.16.0 +requests==2.26.0 +rich==10.7.0 +ruamel.yaml==0.17.11 ; python_version >= "3.7" +ruamel.yaml.clib==0.2.6 snowballstemmer==2.1.0 -sphinx==3.5.4 -sphinx-ansible-theme==0.5.0 +sphinx==4.1.2 +sphinx-ansible-theme==0.8.0 sphinx-rtd-theme==0.5.2 +sphinxcontrib-apidoc==0.3.0 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 @@ -48,10 +54,11 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-programoutput2==2.0a1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -tenacity==7.0.0 -urllib3==1.26.5 +tenacity==8.0.1 +toml==0.10.2 +urllib3==1.26.6 wcmatch==8.2 -yamllint==1.26.1 +yamllint==1.26.2 # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/docs/usage.rst b/docs/usage.rst index 9e5d0a5a6c..0b1012c344 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -66,10 +66,11 @@ also print errors using their `annotation`_ format. Linting Playbooks and Roles --------------------------- -We recommend following the `collection structure layout`_ regardless if you -are planning to build a collection or not. Following that layout assures the -best integration with all ecosystem tools as it helps them better distinguish -between random YAML files and files managed by ansible. +We recommend following the :ref:`collection structure layout +` regardless if you are planning to build a +collection or not. Following that layout assures the best integration +with all ecosystem tools as it helps them better distinguish between +random YAML files and files managed by ansible. When you call ansible-lint without arguments the tool will use its internal heuristics to determine file types. @@ -83,8 +84,6 @@ arguments. The following command lints ``examples/playbooks/play.yml`` and :returncode: 2 :nostderr: -.. _collection structure layout: https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#collection-structure - Examples -------- diff --git a/examples/playbooks/var-spacing.yml b/examples/playbooks/var-spacing.yml index 09973adc1c..e4feb6406e 100644 --- a/examples/playbooks/var-spacing.yml +++ b/examples/playbooks/var-spacing.yml @@ -1,4 +1,4 @@ -# Should raise var-spacing at lines 25, 28, 31 +# Should raise var-spacing at tasks line 23, 26, 29, 54, 65 - hosts: all tasks: - name: good variable format @@ -46,3 +46,25 @@ case2 }} debug: var: cases + + - name: Valid single line nested JSON false positive + debug: + msg: "{{ {'dummy_2': {'nested_dummy_1': 'value_1', 'nested_dummy_2': value_2}} | combine(dummy_1) }}" + + - name: Invalid single line nested JSON + debug: + msg: "{{ {'dummy_2': {'nested_dummy_1': 'value_1', 'nested_dummy_2': value_2}} | combine(dummy_1)}}" + + - name: Valid multiline nested JSON false positive + debug: + msg: >- + {{ {'dummy_2': {'nested_dummy_1': value_1, + 'nested_dummy_2': value_2}} | + combine(dummy_1) }} + + - name: Invalid multiline nested JSON + debug: + msg: >- + {{ {'dummy_2': {'nested_dummy_1': value_1, + 'nested_dummy_2': value_2}} | + combine(dummy_1)}} diff --git a/setup.cfg b/setup.cfg index 08e56afc64..8c77fc1908 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ name = ansible-lint url = https://github.com/ansible-community/ansible-lint project_urls = Bug Tracker = https://github.com/ansible-community/ansible-lint/issues - CI: GitHub = https://github.com/ansible-community/ansible-lint/actions?query=workflow:gh+branch:master+event:push + CI: GitHub = https://github.com/ansible-community/ansible-lint/actions?query=workflow:gh+branch:main+event:push Code of Conduct = https://docs.ansible.com/ansible/latest/community/code_of_conduct.html Documentation = https://ansible-lint.readthedocs.io/en/latest/ Mailing lists = https://docs.ansible.com/ansible-community/latest/community/communication.html#mailing-list-information @@ -103,6 +103,12 @@ core = # yamllint must remain optional yamllint = yamllint >= 1.25.0 # GPLv3 +test = + flaky >= 3.7.0 + pytest >= 6.0.1 + pytest-cov >= 2.10.1 + pytest-xdist >= 2.1.0 + psutil # soft-dep of pytest-xdist [options.packages.find] where = src diff --git a/src/ansiblelint/__main__.py b/src/ansiblelint/__main__.py index 17fe82f11d..1e356e1214 100755 --- a/src/ansiblelint/__main__.py +++ b/src/ansiblelint/__main__.py @@ -151,7 +151,7 @@ def report_outcome( # noqa: C901 if "experimental" in match.rule.tags: entries.append(" - experimental # all rules tagged as experimental\n") break - if entries: + if entries and not options.quiet: console_stderr.print( "You can skip specific rules or tags by adding them to your " "configuration file:" diff --git a/src/ansiblelint/cli.py b/src/ansiblelint/cli.py index 9317350200..3a73096d66 100644 --- a/src/ansiblelint/cli.py +++ b/src/ansiblelint/cli.py @@ -30,10 +30,8 @@ def abspath(path: str, base_dir: str) -> str: Args: path (str): the path to make absolute - base_dir (str): the directory from which make relative paths - absolute - default_drive: Windows drive to use to make the path - absolute if none is given. + base_dir (str): the directory from which make \ + relative paths absolute """ if not os.path.isabs(path): # Don't use abspath as it assumes path is relative to cwd. @@ -46,6 +44,7 @@ def abspath(path: str, base_dir: str) -> str: def expand_to_normalized_paths( config: Dict[str, Any], base_dir: Optional[str] = None ) -> None: + """Mutate given config normalizing any path values in it.""" # config can be None (-c /dev/null) if not config: return @@ -138,6 +137,7 @@ def __call__( def get_cli_parser() -> argparse.ArgumentParser: + """Initialize an argument parser.""" parser = argparse.ArgumentParser() parser.add_argument( @@ -303,6 +303,7 @@ def get_cli_parser() -> argparse.ArgumentParser: def merge_config(file_config: Dict[Any, Any], cli_config: Namespace) -> Namespace: + """Combine the file config with the CLI args.""" bools = ( 'display_relative_path', 'parseable', @@ -371,6 +372,7 @@ def merge_config(file_config: Dict[Any, Any], cli_config: Namespace) -> Namespac def get_config(arguments: List[str]) -> Namespace: + """Extract the config based on given args.""" parser = get_cli_parser() options = parser.parse_args(arguments) @@ -394,6 +396,7 @@ def get_config(arguments: List[str]) -> Namespace: def print_help(file: Any = sys.stdout) -> None: + """Print help test to the given stream.""" get_cli_parser().print_help(file=file) diff --git a/src/ansiblelint/formatters/__init__.py b/src/ansiblelint/formatters/__init__.py index 42f9a0ef29..37372242e2 100644 --- a/src/ansiblelint/formatters/__init__.py +++ b/src/ansiblelint/formatters/__init__.py @@ -159,7 +159,7 @@ class CodeclimateJSONFormatter(BaseFormatter[Any]): The formatter expects a list of MatchError objects and returns a JSON formatted string. The spec for the codeclimate report can be found here: - https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-type + https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#user-content-data-types """ def format_result(self, matches: List["MatchError"]) -> str: diff --git a/src/ansiblelint/prerun.py b/src/ansiblelint/prerun.py index 1471ae6427..0abf82722c 100644 --- a/src/ansiblelint/prerun.py +++ b/src/ansiblelint/prerun.py @@ -106,6 +106,7 @@ def install_collection(collection: str, destination: Optional[str] = None) -> No "ansible-galaxy", "collection", "install", + "--force", # required for ansible 2.9 "-v", ] if destination: @@ -140,6 +141,7 @@ def install_requirements(requirement: str) -> None: "ansible-galaxy", "role", "install", + "--force", # required for ansible 2.9 "--roles-path", f"{options.cache_dir}/roles", "-vr", @@ -165,6 +167,7 @@ def install_requirements(requirement: str) -> None: "ansible-galaxy", "collection", "install", + "--force", # required for ansible 2.9 "-p", f"{options.cache_dir}/collections", "-vr", diff --git a/src/ansiblelint/rules/RoleNames.py b/src/ansiblelint/rules/RoleNames.py index 7dc175dc5b..23e6f2bde7 100644 --- a/src/ansiblelint/rules/RoleNames.py +++ b/src/ansiblelint/rules/RoleNames.py @@ -44,8 +44,8 @@ class RoleNames(AnsibleLintRule): description = ( "Role names are now limited to contain only lowercase alphanumeric " "characters, plus '_' and start with an alpha character. See " - "`developing collections `_" + "`developing collections `_" ) severity = 'HIGH' done: List[str] = [] # already noticed roles list diff --git a/src/ansiblelint/rules/VariableHasSpacesRule.py b/src/ansiblelint/rules/VariableHasSpacesRule.py index e1af823172..0e30c93006 100644 --- a/src/ansiblelint/rules/VariableHasSpacesRule.py +++ b/src/ansiblelint/rules/VariableHasSpacesRule.py @@ -3,7 +3,7 @@ import re import sys -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, List, Optional, Union from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule @@ -20,7 +20,7 @@ class VariableHasSpacesRule(AnsibleLintRule): version_added = 'v4.0.0' bracket_regex = re.compile(r"{{[^{\n' -]|[^ '\n}-]}}", re.MULTILINE | re.DOTALL) - exclude_json_re = re.compile(r"[^{]{'\w+': ?[^{]{.*?}}") + exclude_json_re = re.compile(r"[^{]{'\w+': ?[^{]{.*?}}", re.MULTILINE | re.DOTALL) def matchtask( self, task: Dict[str, Any], file: Optional[Lintable] = None @@ -35,15 +35,36 @@ def matchtask( if 'pytest' in sys.modules: - from ansiblelint.rules import RulesCollection - from ansiblelint.runner import Runner + import pytest - def test_var_spacing() -> None: - """Verify rule.""" + from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports + from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports + + @pytest.fixture + def error_expected_lines() -> List[int]: + """Return list of expected error lines.""" + return [23, 26, 29, 54, 65] + + @pytest.fixture + def test_playbook() -> str: + """Return test cases playbook path.""" + return "examples/playbooks/var-spacing.yml" + + @pytest.fixture + def lint_error_lines(test_playbook: str) -> List[int]: + """Get VarHasSpacesRules linting results on test_playbook.""" collection = RulesCollection() collection.register(VariableHasSpacesRule()) - - lintable = Lintable("examples/playbooks/var-spacing.yml") + lintable = Lintable(test_playbook) results = Runner(lintable, rules=collection).run() + return list(map(lambda item: item.linenumber, results)) - assert len(results) == 3 + def test_var_spacing( + error_expected_lines: List[int], lint_error_lines: List[int] + ) -> None: + """Ensure that expected error lines are matching found linting error lines.""" + # list unexpected error lines or non-matching error lines + error_lines_difference = list( + set(error_expected_lines).symmetric_difference(set(lint_error_lines)) + ) + assert len(error_lines_difference) == 0 diff --git a/src/ansiblelint/skip_utils.py b/src/ansiblelint/skip_utils.py index 8baee4b7b2..e8efbd642b 100644 --- a/src/ansiblelint/skip_utils.py +++ b/src/ansiblelint/skip_utils.py @@ -62,8 +62,8 @@ def append_skipped_rules( :param pyyaml_data: file text parsed via ansible and pyyaml. :param file_text: raw file text. :param file_type: type of file: tasks, handlers or meta. - :returns: original pyyaml_data altered with a 'skipped_rules' list added - to individual tasks, or added to the single metadata block. + :returns: original pyyaml_data altered with a 'skipped_rules' list added \ + to individual tasks, or added to the single metadata block. """ try: yaml_skip = _append_skipped_rules(pyyaml_data, lintable) @@ -80,10 +80,10 @@ def append_skipped_rules( @lru_cache(maxsize=128) def load_data(file_text: str) -> Any: - """Parse `file_text` as yaml and return parsed structure. + """Parse ``file_text`` as yaml and return parsed structure. This is the main culprit for slow performance, each rule asks for loading yaml again and again - ideally the `maxsize` on the decorator above MUST be great or equal total number of rules + ideally the ``maxsize`` on the decorator above MUST be great or equal total number of rules :param file_text: raw text to parse :return: Parsed yaml """ diff --git a/src/ansiblelint/utils.py b/src/ansiblelint/utils.py index 9ea8d2755a..8370740889 100644 --- a/src/ansiblelint/utils.py +++ b/src/ansiblelint/utils.py @@ -88,6 +88,7 @@ def parse_yaml_from_file(filepath: str) -> AnsibleBaseYAMLObject: + """Extract a decrypted YAML object from file.""" dl = DataLoader() if hasattr(dl, 'set_vault_password'): dl.set_vault_password(DEFAULT_VAULT_PASSWORD) @@ -95,6 +96,7 @@ def parse_yaml_from_file(filepath: str) -> AnsibleBaseYAMLObject: def path_dwim(basedir: str, given: str) -> str: + """Convert a given path do-what-I-mean style.""" dl = DataLoader() dl.set_basedir(basedir) return str(dl.path_dwim(given)) @@ -103,6 +105,7 @@ def path_dwim(basedir: str, given: str) -> str: def ansible_template( basedir: str, varname: Any, templatevars: Any, **kwargs: Any ) -> Any: + """Render a templated string.""" # `basedir` is the directory containing the lintable file. # Therefore, for tasks in a role, `basedir` has the form # `roles/some_role/tasks`. On the other hand, the search path @@ -176,6 +179,7 @@ def ansible_template( def tokenize(line: str) -> Tuple[str, List[str], Dict[str, str]]: + """Parse a string task invocation.""" tokens = line.lstrip().split(" ") if tokens[0] == '-': tokens = tokens[1:] @@ -225,6 +229,7 @@ def _set_collections_basedir(basedir: str) -> None: def find_children(lintable: Lintable) -> List[Lintable]: # noqa: C901 + """Traverse children of a single file or folder.""" if not lintable.path.exists(): return [] playbook_dir = str(lintable.path.parent) @@ -276,6 +281,7 @@ def template( fail_on_undefined: bool = False, **kwargs: str, ) -> Any: + """Attempt rendering a value with known vars.""" try: value = ansible_template( os.path.abspath(basedir), @@ -294,6 +300,7 @@ def template( def play_children( basedir: str, item: Tuple[str, Any], parent_type: FileType, playbook_dir: str ) -> List[Lintable]: + """Flatten the traversed play tasks.""" delegate_map: Dict[str, Callable[[str, Any, Any, FileType], List[Lintable]]] = { 'tasks': _taskshandlers_children, 'pre_tasks': _taskshandlers_children, @@ -592,6 +599,7 @@ def normalize_task_v2(task: Dict[str, Any]) -> Dict[str, Any]: def normalize_task(task: Dict[str, Any], filename: str) -> Dict[str, Any]: + """Unify task-like object structures.""" ansible_action_type = task.get('__ansible_action_type__', 'task') if '__ansible_action_type__' in task: del task['__ansible_action_type__'] @@ -602,6 +610,7 @@ def normalize_task(task: Dict[str, Any], filename: str) -> Dict[str, Any]: def task_to_str(task: Dict[str, Any]) -> str: + """Make a string identifier for the given task.""" name = task.get("name") if name: return str(name) @@ -630,6 +639,7 @@ def task_to_str(task: Dict[str, Any]) -> str: def extract_from_list( blocks: AnsibleBaseYAMLObject, candidates: List[str] ) -> List[Any]: + """Get action tasks from block structures.""" results = list() for block in blocks: for candidate in candidates: @@ -645,6 +655,7 @@ def extract_from_list( def add_action_type(actions: AnsibleBaseYAMLObject, action_type: str) -> List[Any]: + """Add action markers to task objects.""" results = list() for action in actions: # ignore empty task @@ -656,6 +667,7 @@ def add_action_type(actions: AnsibleBaseYAMLObject, action_type: str) -> List[An def get_action_tasks(yaml: AnsibleBaseYAMLObject, file: Lintable) -> List[Any]: + """Get a flattened list of action tasks from the file.""" tasks = list() if file.kind in ['tasks', 'handlers']: tasks = add_action_type(yaml, file.kind) @@ -684,6 +696,7 @@ def get_action_tasks(yaml: AnsibleBaseYAMLObject, file: Lintable) -> List[Any]: def get_normalized_tasks( yaml: "AnsibleBaseYAMLObject", file: Lintable ) -> List[Dict[str, Any]]: + """Extract normalized tasks from a file.""" tasks = get_action_tasks(yaml, file) res = [] for task in tasks: @@ -740,6 +753,7 @@ def construct_mapping( def get_first_cmd_arg(task: Dict[str, Any]) -> Any: + """Extract the first arg from a cmd task.""" try: if 'cmd' in task['action']: first_cmd_arg = task['action']['cmd'].split()[0] @@ -751,6 +765,7 @@ def get_first_cmd_arg(task: Dict[str, Any]) -> Any: def get_second_cmd_arg(task: Dict[str, Any]) -> Any: + """Extract the second arg from a cmd task.""" try: if 'cmd' in task['action']: second_cmd_arg = task['action']['cmd'].split()[1] diff --git a/test-requirements.in b/test-requirements.in deleted file mode 100755 index 123f6b6177..0000000000 --- a/test-requirements.in +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env pip-compile -q --allow-unsafe --output-file=test-requirements.txt -# Avoid using --generate-hashes as it breaks pip install from tox with: -# ERROR: In --require-hashes mode, all requirements must have their versions pinned with ==. These do not: -# ansible<2.10,>=2.9 from -flaky >= 3.7.0 -pytest >= 6.0.1 -pytest-cov >= 2.10.1 -pytest-xdist >= 2.1.0 -psutil # soft-dep of pytest-xdist -# Needed to avoid DeprecationWarning errors in pytest: -setuptools >= 49.6.0 diff --git a/test/TestAnsibleLintRule.py b/test/TestAnsibleLintRule.py index 6d6cc2a5b0..e7f4e41912 100644 --- a/test/TestAnsibleLintRule.py +++ b/test/TestAnsibleLintRule.py @@ -8,6 +8,7 @@ def test_unjinja() -> None: + """Verify that unjinja understands nested mustache.""" text = "{{ a }} {% b %} {# try to confuse parsing inside a comment { {{}} } #}" output = "JINJA_EXPRESSION JINJA_STATEMENT JINJA_COMMENT" assert AnsibleLintRule.unjinja(text) == output @@ -15,6 +16,7 @@ def test_unjinja() -> None: @pytest.mark.parametrize('rule_config', (dict(), dict(foo=True, bar=1))) def test_rule_config(rule_config: Dict[str, Any], monkeypatch: MonkeyPatch) -> None: + """Check that a rule config is inherited from options.""" rule_id = 'rule-0' monkeypatch.setattr(AnsibleLintRule, 'id', rule_id) monkeypatch.setitem(options.rules, rule_id, rule_config) diff --git a/test/TestBaseFormatter.py b/test/TestBaseFormatter.py index 4bf65191f0..527839a1d2 100644 --- a/test/TestBaseFormatter.py +++ b/test/TestBaseFormatter.py @@ -19,6 +19,7 @@ def test_base_formatter_when_base_dir( base_dir: Any, relative_path: bool, path: str ) -> None: + """Check that base formatter accepts relative pathlib and str.""" # Given base_formatter = BaseFormatter(base_dir, relative_path) # type: ignore @@ -42,6 +43,7 @@ def test_base_formatter_when_base_dir( def test_base_formatter_when_base_dir_is_given_and_relative_is_true( path: Union[str, Path], base_dir: Union[str, Path] ) -> None: + """Check that the base formatter equally accepts pathlib and str.""" # Given base_formatter = BaseFormatter(base_dir, True) # type: ignore diff --git a/test/TestBecomeUserWithoutBecome.py b/test/TestBecomeUserWithoutBecome.py index 51cdb18eb0..95bee78350 100644 --- a/test/TestBecomeUserWithoutBecome.py +++ b/test/TestBecomeUserWithoutBecome.py @@ -15,10 +15,10 @@ def setUp(self) -> None: def test_file_positive(self) -> None: success = 'examples/playbooks/become-user-without-become-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_file_negative(self) -> None: failure = 'examples/playbooks/become-user-without-become-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(3, len(errs)) + assert len(errs) == 3 diff --git a/test/TestCliRolePaths.py b/test/TestCliRolePaths.py index b2666a8bed..1e6f292379 100644 --- a/test/TestCliRolePaths.py +++ b/test/TestCliRolePaths.py @@ -21,63 +21,49 @@ def test_run_single_role_path_no_trailing_slash_module(self) -> None: role_path = 'roles/test-role' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_single_role_path_no_trailing_slash_script(self) -> None: cwd = self.local_test_dir role_path = 'roles/test-role' result = run_ansible_lint(role_path, cwd=cwd, executable="ansible-lint") - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_single_role_path_with_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = 'roles/test-role/' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_multiple_role_path_no_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = 'roles/test-role' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_multiple_role_path_with_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = 'roles/test-role/' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_inside_role_dir(self) -> None: cwd = os.path.join(self.local_test_dir, 'roles/test-role/') role_path = '.' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_role_three_dir_deep(self) -> None: cwd = self.local_test_dir role_path = 'testproject/roles/test-role' result = run_ansible_lint(role_path, cwd=cwd) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_playbook(self) -> None: """Call ansible-lint the way molecule does.""" @@ -89,9 +75,7 @@ def test_run_playbook(self) -> None: env['ANSIBLE_ROLES_PATH'] = role_path result = run_ansible_lint(lintable, cwd=cwd, env=env) - self.assertIn( - 'Use shell only when shell functionality is required', result.stdout - ) + assert 'Use shell only when shell functionality is required' in result.stdout def test_run_role_name_invalid(self) -> None: cwd = self.local_test_dir diff --git a/test/TestCommandHasChangesCheck.py b/test/TestCommandHasChangesCheck.py index 13bb66155e..f4cccfa790 100644 --- a/test/TestCommandHasChangesCheck.py +++ b/test/TestCommandHasChangesCheck.py @@ -15,10 +15,10 @@ def setUp(self) -> None: def test_command_changes_positive(self) -> None: success = 'examples/playbooks/command-check-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_command_changes_negative(self) -> None: failure = 'examples/playbooks/command-check-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(2, len(errs)) + assert len(errs) == 2 diff --git a/test/TestCommandLineInvocationSameAsConfig.py b/test/TestCommandLineInvocationSameAsConfig.py index 5e519ca14d..647420fd29 100644 --- a/test/TestCommandLineInvocationSameAsConfig.py +++ b/test/TestCommandLineInvocationSameAsConfig.py @@ -10,6 +10,7 @@ @pytest.fixture def base_arguments() -> List[str]: + """Define reusable base arguments for tests in current module.""" return ['../test/skiptasks.yml'] @@ -31,6 +32,7 @@ def base_arguments() -> List[str]: def test_ensure_config_are_equal( base_arguments: List[str], args: List[str], config: str ) -> None: + """Check equality of the CLI options to config files.""" command = base_arguments + args cli_parser = cli.get_cli_parser() @@ -49,6 +51,7 @@ def test_ensure_config_are_equal( def test_config_can_be_overridden(base_arguments: List[str]) -> None: + """Check that config can be overridden from CLI.""" no_override = cli.get_config(base_arguments + ["-t", "bad_tag"]) overridden = cli.get_config( @@ -87,6 +90,7 @@ def test_expand_path_user_and_vars_config_file(base_arguments: List[str]) -> Non def test_path_from_config_do_not_depend_on_cwd( monkeypatch: MonkeyPatch, ) -> None: # Issue 572 + """Check that config-provided paths are decoupled from CWD.""" config1 = cli.load_config("test/fixtures/config-with-relative-path.yml") monkeypatch.chdir('test') config2 = cli.load_config("fixtures/config-with-relative-path.yml") @@ -97,6 +101,7 @@ def test_path_from_config_do_not_depend_on_cwd( def test_path_from_cli_depend_on_cwd( base_arguments: List[str], monkeypatch: MonkeyPatch ) -> None: + """Check that CLI-provided paths are relative to CWD.""" # Issue 572 arguments = base_arguments + [ "--exclude", diff --git a/test/TestComparisonToLiteralBool.py b/test/TestComparisonToLiteralBool.py index 12b25c1327..f8cae4867a 100644 --- a/test/TestComparisonToLiteralBool.py +++ b/test/TestComparisonToLiteralBool.py @@ -63,20 +63,20 @@ def setUp(self) -> None: def test_when(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_when_not_false(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_FALSE) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_when_not_null(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_NULL) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_literal_true(self) -> None: results = self.runner.run_role_tasks_main(FAIL_LITERAL_TRUE) - self.assertEqual(1, len(results)) + assert len(results) == 1 def test_literal_false(self) -> None: results = self.runner.run_role_tasks_main(FAIL_LITERAL_FALSE) - assert len(results) == 2, results + assert len(results) == 2 diff --git a/test/TestDependenciesInMeta.py b/test/TestDependenciesInMeta.py index 15106276a7..a78284b72c 100644 --- a/test/TestDependenciesInMeta.py +++ b/test/TestDependenciesInMeta.py @@ -3,8 +3,7 @@ def test_external_dependency_is_ok(default_rules_collection: RulesCollection) -> None: - playbook_path = 'examples/roles/dependency_in_meta/meta/main.yml'.format_map( - locals() - ) + """Check that external dep in role meta is not a violation.""" + playbook_path = 'examples/roles/dependency_in_meta/meta/main.yml' good_runner = Runner(playbook_path, rules=default_rules_collection) assert [] == good_runner.run() diff --git a/test/TestDeprecatedModule.py b/test/TestDeprecatedModule.py index b7762552bc..e8f79c466c 100644 --- a/test/TestDeprecatedModule.py +++ b/test/TestDeprecatedModule.py @@ -21,7 +21,7 @@ def setUp(self) -> None: def test_module_deprecated(self) -> None: results = self.runner.run_role_tasks_main(MODULE_DEPRECATED) - self.assertEqual(1, len(results)) + assert len(results) == 1 # based on version and blend of ansible being used, we may # get a missing module, so we future proof the test assert ( diff --git a/test/TestEnvVarsInCommand.py b/test/TestEnvVarsInCommand.py index 9f8866968e..99782d952f 100644 --- a/test/TestEnvVarsInCommand.py +++ b/test/TestEnvVarsInCommand.py @@ -83,8 +83,8 @@ def setUp(self) -> None: def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_PLAY_TASKS) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_PLAY_TASKS) - self.assertEqual(2, len(results)) + assert len(results) == 2 diff --git a/test/TestImportIncludeRole.py b/test/TestImportIncludeRole.py index 54f9fd7e2a..d7ff15eb63 100644 --- a/test/TestImportIncludeRole.py +++ b/test/TestImportIncludeRole.py @@ -50,6 +50,7 @@ @pytest.fixture def playbook_path(request: SubRequest, tmp_path: Path) -> str: + """Create a reusable per-test role skeleton.""" playbook_text = request.param role_tasks_dir = tmp_path / 'test-role' / 'tasks' role_tasks_dir.mkdir(parents=True) @@ -89,6 +90,7 @@ def playbook_path(request: SubRequest, tmp_path: Path) -> str: def test_import_role2( default_rules_collection: RulesCollection, playbook_path: str, messages: List[str] ) -> None: + """Test that include_role digs deeper than import_role.""" runner = Runner(playbook_path, rules=default_rules_collection) results = runner.run() for message in messages: diff --git a/test/TestImportWithMalformed.py b/test/TestImportWithMalformed.py index 0c92ab1ae8..d174c10de8 100644 --- a/test/TestImportWithMalformed.py +++ b/test/TestImportWithMalformed.py @@ -41,6 +41,7 @@ ) @pytest.mark.usefixtures('_play_files') def test_import_tasks_with_malformed_import(runner: Runner) -> None: + """Test that malformed tasks/imports don't fail parsing.""" results = runner.run() passed = False for result in results: diff --git a/test/TestIncludeMissFileWithRole.py b/test/TestIncludeMissFileWithRole.py index d01dbc826f..c41462ecf2 100644 --- a/test/TestIncludeMissFileWithRole.py +++ b/test/TestIncludeMissFileWithRole.py @@ -85,6 +85,7 @@ ) @pytest.mark.usefixtures('_play_files') def test_cases_warning_message(runner: Runner, caplog: LogCaptureFixture) -> None: + """Test that including a non-existing file produces an error.""" result = runner.run() assert len(result) == 1 @@ -111,6 +112,7 @@ def test_cases_warning_message(runner: Runner, caplog: LogCaptureFixture) -> Non ) @pytest.mark.usefixtures('_play_files') def test_cases_that_do_not_report(runner: Runner, caplog: LogCaptureFixture) -> None: + """Test that relative inclusions are properly followed.""" runner.run() noexist_message_count = 0 diff --git a/test/TestLineTooLong.py b/test/TestLineTooLong.py index b1f7dc08a0..44e2b378f3 100644 --- a/test/TestLineTooLong.py +++ b/test/TestLineTooLong.py @@ -21,4 +21,4 @@ def setUp(self) -> None: def test_long_line(self) -> None: results = self.runner.run_role_tasks_main(LONG_LINE) - self.assertEqual(1, len(results)) + assert len(results) == 1 diff --git a/test/TestLintRule.py b/test/TestLintRule.py index 8c263d087d..7889f5191c 100644 --- a/test/TestLintRule.py +++ b/test/TestLintRule.py @@ -31,10 +31,10 @@ def test_rule_matching(self) -> None: ematcher = EMatcherRule.EMatcherRule() lintable = Lintable('examples/playbooks/ematcher-rule.yml', kind="playbook") matches = ematcher.matchlines(lintable) - self.assertEqual(len(matches), 3) + assert len(matches) == 3 def test_rule_postmatching(self) -> None: rule = UnsetVariableMatcherRule.UnsetVariableMatcherRule() lintable = Lintable('examples/playbooks/bracketsmatchtest.yml', kind="playbook") matches = rule.matchlines(lintable) - self.assertEqual(len(matches), 2) + assert len(matches) == 2 diff --git a/test/TestMetaChangeFromDefault.py b/test/TestMetaChangeFromDefault.py index 1cff94e57a..b77ac818a5 100644 --- a/test/TestMetaChangeFromDefault.py +++ b/test/TestMetaChangeFromDefault.py @@ -23,7 +23,7 @@ def setUp(self) -> None: def test_default_galaxy_info(self) -> None: results = self.runner.run_role_meta_main(DEFAULT_GALAXY_INFO) - self.assertIn("Should change default metadata: author", str(results)) - self.assertIn("Should change default metadata: description", str(results)) - self.assertIn("Should change default metadata: company", str(results)) - self.assertIn("Should change default metadata: license", str(results)) + assert "Should change default metadata: author" in str(results) + assert "Should change default metadata: description" in str(results) + assert "Should change default metadata: company" in str(results) + assert "Should change default metadata: license" in str(results) diff --git a/test/TestMetaMainHasInfo.py b/test/TestMetaMainHasInfo.py index ffff58a0d2..984e551ff5 100644 --- a/test/TestMetaMainHasInfo.py +++ b/test/TestMetaMainHasInfo.py @@ -66,23 +66,23 @@ def setUp(self) -> None: def test_no_galaxy_info(self) -> None: results = self.runner.run_role_meta_main(NO_GALAXY_INFO) assert len(results) == 1 - self.assertIn("No 'galaxy_info' found", str(results)) + assert "No 'galaxy_info' found" in str(results) def test_missing_info(self) -> None: results = self.runner.run_role_meta_main(MISSING_INFO) assert len(results) == 3 - self.assertIn("Role info should contain author", str(results)) - self.assertIn("Role info should contain min_ansible_version", str(results)) - self.assertIn("Platform should contain name", str(results)) + assert "Role info should contain author" in str(results) + assert "Role info should contain min_ansible_version" in str(results) + assert "Platform should contain name" in str(results) def test_bad_types(self) -> None: results = self.runner.run_role_meta_main(BAD_TYPES) assert len(results) == 3 - self.assertIn("author should be a string", str(results)) - self.assertIn("description should be a string", str(results)) - self.assertIn("Platforms should be a list of dictionaries", str(results)) + assert "author should be a string" in str(results) + assert "description should be a string" in str(results) + assert "Platforms should be a list of dictionaries" in str(results) def test_platform_list_of_str(self) -> None: results = self.runner.run_role_meta_main(PLATFORMS_LIST_OF_STR) assert len(results) == 1 - self.assertIn("Platforms should be a list of dictionaries", str(results)) + assert "Platforms should be a list of dictionaries" in str(results) diff --git a/test/TestMetaVideoLinks.py b/test/TestMetaVideoLinks.py index 0270cd360d..109c79005b 100644 --- a/test/TestMetaVideoLinks.py +++ b/test/TestMetaVideoLinks.py @@ -27,8 +27,6 @@ def setUp(self) -> None: def test_video_links(self) -> None: results = self.runner.run_role_meta_main(META_VIDEO_LINKS) - self.assertIn("Expected item in 'video_links' to be a dictionary", str(results)) - self.assertIn( - "'video_links' to contain only keys 'url' and 'title'", str(results) - ) - self.assertIn("URL format 'www.myvid.com/vid' is not recognized", str(results)) + assert "Expected item in 'video_links' to be a dictionary" in str(results) + assert "'video_links' to contain only keys 'url' and 'title'" in str(results) + assert "URL format 'www.myvid.com/vid' is not recognized" in str(results) diff --git a/test/TestNestedJinjaRule.py b/test/TestNestedJinjaRule.py index a38e0a0dd4..10bc38a95d 100644 --- a/test/TestNestedJinjaRule.py +++ b/test/TestNestedJinjaRule.py @@ -221,6 +221,7 @@ def _playbook_file(tmp_path: Path, request: SubRequest) -> None: ) @pytest.mark.usefixtures('_playbook_file') def test_including_wrong_nested_jinja(runner: Runner) -> None: + """Check that broken Jinja nesting produces a violation.""" rule_violations = runner.run() assert rule_violations[0].rule.id == 'no-jinja-nesting' @@ -250,5 +251,6 @@ def test_including_wrong_nested_jinja(runner: Runner) -> None: ) @pytest.mark.usefixtures('_playbook_file') def test_including_proper_nested_jinja(runner: Runner) -> None: + """Check that properly balanced Jinja nesting doesn't fail.""" rule_violations = runner.run() assert not rule_violations diff --git a/test/TestNoFormattingInWhenRule.py b/test/TestNoFormattingInWhenRule.py index 80c5a5f2e2..24fe92172e 100644 --- a/test/TestNoFormattingInWhenRule.py +++ b/test/TestNoFormattingInWhenRule.py @@ -15,10 +15,10 @@ def setUp(self) -> None: def test_file_positive(self) -> None: success = 'examples/playbooks/jinja2-when-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_file_negative(self) -> None: failure = 'examples/playbooks/jinja2-when-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(2, len(errs)) + assert len(errs) == 2 diff --git a/test/TestOctalPermissions.py b/test/TestOctalPermissions.py index 81d3f7b262..3ec87fee12 100644 --- a/test/TestOctalPermissions.py +++ b/test/TestOctalPermissions.py @@ -136,22 +136,20 @@ def setUp(self) -> None: def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_TASKS) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_TASKS) - self.assertEqual(4, len(results)) + assert len(results) == 4 def test_valid_modes(self) -> None: for mode in self.VALID_MODES: - self.assertFalse( - self.rule.is_invalid_permission(mode), - msg="0o%o should be a valid mode" % mode, + assert not self.rule.is_invalid_permission(mode), ( + "0o%o should be a valid mode" % mode ) def test_invalid_modes(self) -> None: for mode in self.INVALID_MODES: - self.assertTrue( - self.rule.is_invalid_permission(mode), - msg="%d should be an invalid mode" % mode, + assert self.rule.is_invalid_permission(mode), ( + "%d should be an invalid mode" % mode ) diff --git a/test/TestPackageIsNotLatest.py b/test/TestPackageIsNotLatest.py index 17ac009473..b21f38d0cb 100644 --- a/test/TestPackageIsNotLatest.py +++ b/test/TestPackageIsNotLatest.py @@ -15,10 +15,10 @@ def setUp(self) -> None: def test_package_not_latest_positive(self) -> None: success = 'examples/playbooks/package-check-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_package_not_latest_negative(self) -> None: failure = 'examples/playbooks/package-check-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(4, len(errs)) + assert len(errs) == 4 diff --git a/test/TestRoleRelativePath.py b/test/TestRoleRelativePath.py index 4dc920d39e..627c8ed925 100644 --- a/test/TestRoleRelativePath.py +++ b/test/TestRoleRelativePath.py @@ -46,8 +46,8 @@ def setUp(self) -> None: def test_fail(self) -> None: results = self.runner.run_role_tasks_main(FAIL_TASKS) - self.assertEqual(2, len(results)) + assert len(results) == 2 def test_success(self) -> None: results = self.runner.run_role_tasks_main(SUCCESS_TASKS) - self.assertEqual(0, len(results)) + assert len(results) == 0 diff --git a/test/TestRuleProperties.py b/test/TestRuleProperties.py index dba0cf2124..61c082421a 100644 --- a/test/TestRuleProperties.py +++ b/test/TestRuleProperties.py @@ -2,6 +2,7 @@ def test_serverity_valid(default_rules_collection: RulesCollection) -> None: + """Test that rules collection only has allow-listed severities.""" valid_severity_values = [ 'VERY_HIGH', 'HIGH', diff --git a/test/TestRulesCollection.py b/test/TestRulesCollection.py index 6a5b59d015..33fef4bace 100644 --- a/test/TestRulesCollection.py +++ b/test/TestRulesCollection.py @@ -32,20 +32,24 @@ @pytest.fixture def test_rules_collection() -> RulesCollection: + """Create a shared rules collection test instance.""" return RulesCollection([os.path.abspath('./test/rules')]) @pytest.fixture def ematchtestfile() -> Lintable: + """Produce a test lintable with an id violation.""" return Lintable('examples/playbooks/ematcher-rule.yml', kind='playbook') @pytest.fixture def bracketsmatchtestfile() -> Lintable: + """Produce a test lintable with matching brackets.""" return Lintable('examples/playbooks/bracketsmatchtest.yml', kind='playbook') def test_load_collection_from_directory(test_rules_collection: RulesCollection) -> None: + """Test that custom rules extend the default ones.""" # two detected rules plus the internal ones assert len(test_rules_collection) == 5 @@ -53,6 +57,7 @@ def test_load_collection_from_directory(test_rules_collection: RulesCollection) def test_run_collection( test_rules_collection: RulesCollection, ematchtestfile: Lintable ) -> None: + """Test that default rules match pre-meditated violations.""" matches = test_rules_collection.run(ematchtestfile) assert len(matches) == 3 # 3 occurrences of BANNED using TEST0001 assert matches[0].linenumber == 2 @@ -63,6 +68,7 @@ def test_tags( ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: + """Test that tags are treated as skip markers.""" matches = test_rules_collection.run(ematchtestfile, tags={'test1'}) assert len(matches) == 3 matches = test_rules_collection.run(ematchtestfile, tags={'test2'}) @@ -78,6 +84,7 @@ def test_skip_tags( ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: + """Test that tags can be skipped.""" matches = test_rules_collection.run(ematchtestfile, skip_list=['test1']) assert len(matches) == 0 matches = test_rules_collection.run(ematchtestfile, skip_list=['test2']) @@ -93,6 +100,7 @@ def test_skip_id( ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: + """Check that skipping valid IDs excludes their violations.""" matches = test_rules_collection.run(ematchtestfile, skip_list=['TEST0001']) assert len(matches) == 0 matches = test_rules_collection.run(ematchtestfile, skip_list=['TEST0002']) @@ -106,11 +114,13 @@ def test_skip_id( def test_skip_non_existent_id( test_rules_collection: RulesCollection, ematchtestfile: Lintable ) -> None: + """Check that skipping invalid IDs changes nothing.""" matches = test_rules_collection.run(ematchtestfile, skip_list=['DOESNOTEXIST']) assert len(matches) == 3 def test_no_duplicate_rule_ids(test_rules_collection: RulesCollection) -> None: + """Check that rules of the collection don't have duplicate IDs.""" real_rules = RulesCollection([os.path.abspath('./src/ansiblelint/rules')]) rule_ids = [rule.id for rule in real_rules] assert not any(y > 1 for y in collections.Counter(rule_ids).values()) diff --git a/test/TestRunner.py b/test/TestRunner.py index ba767193b8..bd8fde2339 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -56,6 +56,7 @@ def test_runner( exclude: List[str], length: int, ) -> None: + """Test that runner can go through any corner cases.""" runner = Runner(playbook, rules=default_rules_collection, exclude_paths=exclude) matches = runner.run() @@ -100,6 +101,7 @@ def test_runner_unicode_format( default_rules_collection: RulesCollection, formatter_cls: Type[formatters.BaseFormatter[Any]], ) -> None: + """Check that all formatters are unicode-friendly.""" formatter = formatter_cls(os.getcwd(), display_relative_path=True) runner = Runner( Lintable('examples/playbooks/unicode.yml', "playbook"), @@ -115,6 +117,7 @@ def test_runner_unicode_format( def test_runner_with_directory( default_rules_collection: RulesCollection, directory_name: str ) -> None: + """Check that runner detects a directory as role.""" runner = Runner(directory_name, rules=default_rules_collection) expected = Lintable(name=directory_name, kind="role") @@ -122,6 +125,7 @@ def test_runner_with_directory( def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> None: + """Ensure that lintables aren't double-checked.""" checked_files: Set[Lintable] = set() filename = os.path.abspath('examples/playbooks/common-include-1.yml') diff --git a/test/TestShellWithoutPipefail.py b/test/TestShellWithoutPipefail.py index 1f10b12e41..4b794b8c5e 100644 --- a/test/TestShellWithoutPipefail.py +++ b/test/TestShellWithoutPipefail.py @@ -87,8 +87,8 @@ def setUp(self) -> None: def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_TASKS) - self.assertEqual(3, len(results)) + assert len(results) == 3 def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_TASKS) - self.assertEqual(0, len(results)) + assert len(results) == 0 diff --git a/test/TestSkipImportPlaybook.py b/test/TestSkipImportPlaybook.py index bf13cade4c..e069546be7 100644 --- a/test/TestSkipImportPlaybook.py +++ b/test/TestSkipImportPlaybook.py @@ -26,6 +26,7 @@ @pytest.fixture def playbook(tmp_path: Path) -> str: + """Create a reusable per-test playbook.""" playbook_path = tmp_path / 'playbook.yml' playbook_path.write_text(MAIN_PLAYBOOK) (tmp_path / 'imported_playbook.yml').write_text(IMPORTED_PLAYBOOK) @@ -35,6 +36,7 @@ def playbook(tmp_path: Path) -> str: def test_skip_import_playbook( default_rules_collection: RulesCollection, playbook: str ) -> None: + """Verify that a playbook import is skipped after a failure.""" runner = Runner(playbook, rules=default_rules_collection) results = runner.run() assert len(results) == 0 diff --git a/test/TestSkipInsideYaml.py b/test/TestSkipInsideYaml.py index aab46feae3..da6a6b41a1 100644 --- a/test/TestSkipInsideYaml.py +++ b/test/TestSkipInsideYaml.py @@ -90,6 +90,7 @@ def test_role_tasks(default_text_runner: RunFromText) -> None: + """Check that role tasks can contain skips.""" results = default_text_runner.run_role_tasks_main(ROLE_TASKS) assert len(results) == 1, results assert results[0].linenumber == 2 @@ -97,6 +98,7 @@ def test_role_tasks(default_text_runner: RunFromText) -> None: def test_role_tasks_with_block(default_text_runner: RunFromText) -> None: + """Check that blocks in role tasks can contain skips.""" results = default_text_runner.run_role_tasks_main(ROLE_TASKS_WITH_BLOCK) assert len(results) == 4 @@ -112,10 +114,12 @@ def test_role_tasks_with_block(default_text_runner: RunFromText) -> None: def test_playbook( default_text_runner: RunFromText, playbook_src: str, results_num: int ) -> None: + """Check that playbooks can contain skips.""" results = default_text_runner.run_playbook(playbook_src) assert len(results) == results_num def test_role_meta(default_text_runner: RunFromText) -> None: + """Check that role meta can contain skips.""" results = default_text_runner.run_role_meta_main(ROLE_META) assert len(results) == 0 diff --git a/test/TestSkipPlaybookItems.py b/test/TestSkipPlaybookItems.py index 5041150026..8e5a5f290b 100644 --- a/test/TestSkipPlaybookItems.py +++ b/test/TestSkipPlaybookItems.py @@ -99,6 +99,7 @@ def test_pre_tasks( default_text_runner: RunFromText, playbook: str, length: int ) -> None: + """Check that skipping is possible in different playbook parts.""" # When results = default_text_runner.run_playbook(playbook) diff --git a/test/TestTaskHasName.py b/test/TestTaskHasName.py index 1de8ef6d0e..6076c2f91d 100644 --- a/test/TestTaskHasName.py +++ b/test/TestTaskHasName.py @@ -15,10 +15,10 @@ def setUp(self) -> None: def test_file_positive(self) -> None: success = 'examples/playbooks/task-has-name-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_file_negative(self) -> None: failure = 'examples/playbooks/task-has-name-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(4, len(errs)) + assert len(errs) == 4 diff --git a/test/TestTaskNoLocalAction.py b/test/TestTaskNoLocalAction.py index 13b697cbfa..cc8f42a901 100644 --- a/test/TestTaskNoLocalAction.py +++ b/test/TestTaskNoLocalAction.py @@ -21,4 +21,4 @@ def setUp(self) -> None: def test_local_action(self) -> None: results = self.runner.run_role_tasks_main(TASK_LOCAL_ACTION) - self.assertEqual(1, len(results)) + assert len(results) == 1 diff --git a/test/TestUseHandlerRatherThanWhenChanged.py b/test/TestUseHandlerRatherThanWhenChanged.py index 7ac6da1f14..cb8a086fcd 100644 --- a/test/TestUseHandlerRatherThanWhenChanged.py +++ b/test/TestUseHandlerRatherThanWhenChanged.py @@ -81,8 +81,8 @@ def setUp(self) -> None: def test_success(self) -> None: results = self.runner.run_role_tasks_main(SUCCESS_TASKS) - self.assertEqual(0, len(results)) + assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_role_tasks_main(FAIL_TASKS) - self.assertEqual(5, len(results)) + assert len(results) == 5 diff --git a/test/TestUsingBareVariablesIsDeprecated.py b/test/TestUsingBareVariablesIsDeprecated.py index 11b1467b49..35e6b46e71 100644 --- a/test/TestUsingBareVariablesIsDeprecated.py +++ b/test/TestUsingBareVariablesIsDeprecated.py @@ -17,10 +17,10 @@ def setUp(self) -> None: def test_file_positive(self) -> None: success = 'examples/playbooks/using-bare-variables-success.yml' good_runner = Runner(success, rules=self.collection) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_file_negative(self) -> None: failure = 'examples/playbooks/using-bare-variables-failure.yml' bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() - self.assertEqual(11, len(errs)) + assert len(errs) == 11 diff --git a/test/TestWithSkipTagId.py b/test/TestWithSkipTagId.py index 3d82bc2edd..05c7ae56b4 100644 --- a/test/TestWithSkipTagId.py +++ b/test/TestWithSkipTagId.py @@ -14,13 +14,13 @@ class TestWithSkipTagId(unittest.TestCase): def test_negative_no_param(self) -> None: bad_runner = Runner(self.file, rules=self.collection) errs = bad_runner.run() - self.assertGreater(len(errs), 0) + assert len(errs) > 0 def test_negative_with_id(self) -> None: with_id = 'yaml' bad_runner = Runner(self.file, rules=self.collection, tags=frozenset([with_id])) errs = bad_runner.run() - self.assertGreater(len(errs), 0) + assert len(errs) > 0 def test_negative_with_tag(self) -> None: with_tag = 'trailing-spaces' @@ -28,14 +28,14 @@ def test_negative_with_tag(self) -> None: self.file, rules=self.collection, tags=frozenset([with_tag]) ) errs = bad_runner.run() - self.assertGreater(len(errs), 0) + assert len(errs) > 0 def test_positive_skip_id(self) -> None: skip_id = 'yaml' good_runner = Runner(self.file, rules=self.collection, skip_list=[skip_id]) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() def test_positive_skip_tag(self) -> None: skip_tag = 'trailing-spaces' good_runner = Runner(self.file, rules=self.collection, skip_list=[skip_tag]) - self.assertEqual([], good_runner.run()) + assert [] == good_runner.run() diff --git a/tox.ini b/tox.ini index f10e2d6f34..9cb5437739 100644 --- a/tox.ini +++ b/tox.ini @@ -19,13 +19,11 @@ description = extras = yamllint core: core - ; devel: devel + test deps = ansible29: ansible>=2.9,<2.10 py: ansible-core>=2.11 devel: ansible-core @ git+https://github.com/ansible/ansible.git # GPLv3+ - -r test-requirements.in - -c test-requirements.txt commands = # safety measure to assure we do not accidentally run tests with broken dependencies {envpython} -m pip check @@ -59,6 +57,7 @@ passenv = # recreate = True setenv = COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}} + PIP_CONSTRAINT = {toxinidir}/constraints.txt PIP_DISABLE_PIP_VERSION_CHECK = 1 PRE_COMMIT_COLOR = always FORCE_COLOR = 1 @@ -67,7 +66,9 @@ whitelist_externals = [testenv:lint] description = Run all linters -basepython = python3 +# pip compile includes python version in output constraints, so we want to +# be sure that version does not change randomly. +basepython = python3.9 deps = pre-commit>=2.6.0 pip-tools>=6.2.0 @@ -84,7 +85,11 @@ description = Bump all test depeendencies # we reuse the lint environment envdir = {toxworkdir}/lint skip_install = true -deps = {[testenv:lint]deps} +deps = + pre-commit>=2.14.0 +setenv = + # without his upgrade would likely not do anything + PIP_CONSTRAINT = /dev/null commands = # manual hook calls the optional pip-compile-upgrade hook {[testenv:lint]commands} --hook-stage manual pip-compile-upgrade @@ -95,7 +100,8 @@ description = Builds docs basepython = python3 deps = -r{toxinidir}/docs/requirements.in - -c{toxinidir}/docs/requirements.txt +setenv = + PIP_CONSTRAINT = {toxinidir}/docs/requirements.txt commands = # Build the html docs with Sphinx: {envpython} -m sphinx \ @@ -104,7 +110,7 @@ commands = --color \ -a \ -n \ - -W \ + -W --keep-going \ -d "{temp_dir}/.doctrees" \ . \ "{envdir}/html" @@ -115,6 +121,25 @@ commands = 'print("\n" + "=" * 120 + f"\n\nDocumentation available under `file://\{index_file\}`\n\nTo serve docs, use `python3 -m http.server --directory \{docs_dir\} 0`\n\n" + "=" * 120)' changedir = {toxinidir}/docs +[testenv:linkcheck-docs] +description = Linkcheck The Docs +basepython = {[testenv:docs]basepython} +deps = {[testenv:docs]deps} +setenv = + {[testenv:docs]setenv} +commands = + {envpython} -m sphinx \ + -j auto \ + -b linkcheck \ + -a \ + -n \ + -W --keep-going \ + {tty:--color} \ + -d "{temp_dir}/.doctrees" \ + . \ + "{envdir}/html" +changedir = {[testenv:docs]changedir} + [testenv:eco] description = Perform ecosystem impact (downstream testing) https://github.com/ansible-community/ansible-lint/discussions/1403 commands = @@ -134,7 +159,7 @@ basepython = python3 description = Build package, verify metadata, install package and assert behavior when ansible is missing. deps = - pep517 >= 0.7.0 + build >= 0.6.0, < 0.7.0 twine skip_install = true # Ref: https://twitter.com/di_codes/status/1044358639081975813 @@ -145,13 +170,11 @@ commands = os.path.isdir(dist_dir) or sys.exit(0); \ print("Removing \{!s\} contents...".format(dist_dir), file=sys.stderr); \ shutil.rmtree(dist_dir)' - {envpython} -m pep517.build \ - --source \ - --binary \ - --out-dir {toxinidir}/dist/ \ + {envpython} -m build \ + --outdir {toxinidir}/dist/ \ {toxinidir} # Validate metadata using twine - twine check {toxinidir}/dist/* + twine check --strict {toxinidir}/dist/* # Install the wheel sh -c "python3 -m pip install {toxinidir}/dist/*.whl" # Re-assure ansible is not installed