diff --git a/.config/requirements-dev.in b/.config/requirements-dev.in deleted file mode 100644 index fe0aabf..0000000 --- a/.config/requirements-dev.in +++ /dev/null @@ -1,8 +0,0 @@ -black -mypy -pre-commit -pydoclint -pytest -ruff -toml-sort -types-PyYAML diff --git a/.config/requirements-test.in b/.config/requirements-test.in index d6333f2..b697209 100644 --- a/.config/requirements-test.in +++ b/.config/requirements-test.in @@ -1,4 +1,12 @@ -pytest +black coverage[toml] +mypy +pip-tools +pre-commit +pydoclint +pytest pytest-xdist +ruff +toml-sort tox +types-PyYAML diff --git a/.gitignore b/.gitignore index 1b1aca1..f409493 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,7 @@ cython_debug/ # Version created and populated by setuptools_scm /src/*/_version.py +# Coverage files +*.lcov + .DS_Store diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 37de469..d26386e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,13 @@ { "recommendations": [ + "charliermarsh.ruff", + "esbenp.prettier-vscode", + "markis.code-coverage", "ms-python.black-formatter", + "ms-python.debugpy", + "ms-python.mypy-type-checker", "ms-python.pylint", - "charliermarsh.ruff", - "matangover.mypy", - "esbenp.prettier-vscode" + "ms-python.python", + "streetsidesoftware.code-spell-checker" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 95b3f85..77ac4e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,20 @@ { - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - // Override the pyproject.toml and disable xdist to enable test debugging - "python.testing.pytestArgs": ["-n0", "--dist", "no"], + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, "[python]": { - "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": "explicit" + "source.fixAll": "explicit", + "source.organizeImports": "explicit" }, - "editor.defaultFormatter": "charliermarsh.ruff" + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true }, - "mypy.runUsingActiveInterpreter": true, - "mypy.targets": ["src", "tests"], - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - } + "markiscodecoverage.searchCriteria": "coverage.lcov", + "mypy-type-checker.importStrategy": "fromEnvironment", + "mypy-type-checker.preferDaemon": true, + "mypy-type-checker.reportingScope": "workspace", + "python.testing.pytestArgs": ["tests"], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false } diff --git a/pyproject.toml b/pyproject.toml index f442f98..f6eae69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,12 +52,12 @@ skip_covered = true skip_empty = true [tool.coverage.run] -concurrency = ["multiprocessing", "thread"] +source_pkgs = ["ansible_dev_environment"] # Do not use branch until bug is fixes: # https://github.com/nedbat/coveragepy/issues/605 # branch = true parallel = true -source = ["src"] +concurrency = ["multiprocessing", "thread"] [tool.mypy] files = ["src", "tests"] @@ -308,33 +308,42 @@ disable = [ "unsubscriptable-object" ] +[tool.pytest.ini_options] +addopts = "-ra --showlocals --durations=10" +testpaths = "tests" +tmp_path_retention_policy = "failed" +verbosity_assertions = 2 + [tool.ruff] builtins = ["__"] fix = true line-length = 100 -select = ["ALL"] target-version = "py310" -[tool.ruff.flake8-pytest-style] +[tool.ruff.lint] +select = ["ALL"] + +[tool.ruff.lint.flake8-pytest-style] parametrize-values-type = "tuple" -[tool.ruff.isort] +[tool.ruff.lint.isort] lines-after-imports = 2 # Ensures consistency for cases when there's variable vs function/class definitions after imports lines-between-types = 1 # Separate import/from with 1 line -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # SLF001: Allow private member access in tests # S101 Allow assert in tests # S602 Allow shell in test # T201 Allow print in tests "tests/**" = ["SLF001", "S101", "S602", "T201"] +# SIM108, file is generated +"_version.py" = ["SIM108"] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "pep257" [tool.setuptools.dynamic] dependencies = {file = [".config/requirements.in"]} -optional-dependencies.dev = {file = [".config/requirements-dev.in"]} optional-dependencies.docs = {file = [".config/requirements-docs.in"]} optional-dependencies.test = {file = [".config/requirements-test.in"]} diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0feff89 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +"""Global conftest.py for pytest. + +The root package import below happens before the pytest workers are forked, so it +picked up by the initial coverage process for a source match. + +Without it, coverage reports the following false positive error: + +CoverageWarning: No data was collected. (no-data-collected) + +This works in conjunction with the coverage source_pkg set to the package such that +a `coverage run --debug trace` shows the source package and file match. + +<...> +Imported source package 'ansible_dev_environment' as '/**/src//__init__.py' +<...> +Tracing '/**/src//__init__.py' +""" + +import ansible_dev_environment # noqa: F401 diff --git a/tox.ini b/tox.ini index f1bff98..baee09c 100644 --- a/tox.ini +++ b/tox.ini @@ -25,20 +25,23 @@ pass_env = TERM USER set_env = + COVERAGE_FILE = {env:COVERAGE_FILE:{envdir}/.coverage.{envname}} COVERAGE_PROCESS_START = {toxinidir}/pyproject.toml FORCE_COLOR = 1 PIP_CONSTRAINT = {toxinidir}/.config/constraints.txt PRE_COMMIT_COLOR = always TERM = xterm-256color commands_pre = - sh -c "rm -f .coverage* coverage.xml 2>/dev/null || true" + python -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")' + sh -c "rm -f {envdir}/.coverage* 2>/dev/null || true" commands = - coverage run -m pytest {posargs} -commands_post = - py,py{39,310,311,312}: sh -c "coverage combine .coverage.*" - py,py{39,310,311,312}: coverage xml - py,py{39,310,311,312}: coverage report - git diff --exit-code + coverage run -m pytest {posargs:-n auto} + {py,py310,py311,py312,py313}: sh -c " \ + coverage combine -q --data-file={envdir}/.coverage {envdir}/.coverage.* && \ + coverage xml --data-file={envdir}/.coverage -o {envdir}/coverage.xml --ignore-errors --fail-under=0 && \ + COVERAGE_FILE={envdir}/.coverage coverage lcov --fail-under=0 --ignore-errors -q && \ + COVERAGE_FILE={envdir}/.coverage coverage report --fail-under=0 --ignore-errors \ + " allowlist_externals = git rm