From 6866b3b41911ddda1e56bdce3393b748e95053f4 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Thu, 8 Aug 2024 09:38:56 +0100 Subject: [PATCH] Adopt bindep functionality from tox-bindep plugin (#93) --- README.md | 20 +++++++++--- pyproject.toml | 2 ++ src/tox_extra/bindep.py | 50 ++++++++++++++++++++++++++++++ src/tox_extra/hooks.py | 11 ++++++- tests/__init__.py | 18 +++++++++++ tests/fixtures/bindep_0/bindep.txt | 1 + tests/fixtures/bindep_0/tox.ini | 2 ++ tests/fixtures/bindep_1/bindep.txt | 1 + tests/fixtures/bindep_1/tox.ini | 2 ++ tests/fixtures/bindep_2/bindep.txt | 1 + tests/fixtures/bindep_2/tox.ini | 2 ++ tests/fixtures/bindep_3/bindep.txt | 0 tests/test_bindep.py | 26 ++++++++++++++++ tests/test_plugin.py | 17 ++-------- tox.ini | 12 ++++--- 15 files changed, 141 insertions(+), 24 deletions(-) create mode 100644 src/tox_extra/bindep.py create mode 100644 tests/__init__.py create mode 100644 tests/fixtures/bindep_0/bindep.txt create mode 100644 tests/fixtures/bindep_0/tox.ini create mode 100644 tests/fixtures/bindep_1/bindep.txt create mode 100644 tests/fixtures/bindep_1/tox.ini create mode 100644 tests/fixtures/bindep_2/bindep.txt create mode 100644 tests/fixtures/bindep_2/tox.ini create mode 100644 tests/fixtures/bindep_3/bindep.txt create mode 100644 tests/test_bindep.py diff --git a/README.md b/README.md index 140b285..f53a9a1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # tox-extra -This [tox plugin](https://github.com/topics/tox-plugin) adds few extra checks +This [tox plugin](https://github.com/topics/tox-plugin) adds a few extra checks like: -- ensure exit code 1 if git reports dirty or untracked files _after_ the run +- [tox-extra](#tox-extra) + - [Checks Git Dirty Status](#checks-git-dirty-status) + - [Checks system dependencies using bindep](#checks-system-dependencies-using-bindep) + +## Checks Git Dirty Status + +It ensures exit code 1 if git reports dirty or untracked files _after_ the run. Usage example: -``` +```shell $ tox -e py ... ERROR: Git reported dirty status. Git should never report dirty status at the end of testing, regardless if status is passed, failed or aborted. @@ -23,7 +29,7 @@ __________________________________________ summary _____________________________ ERROR: py: failed ``` -The goal of this plugin is to help developers be aware about files modified by tests +The goal of this plugin is to help developers be aware of files modified by tests or untracked files before they commit the code. This plugin also does not take into consideration the global `.gitignore`, something that can make git miss reporting some untracked files, the goal being to assure that when a new developer clones and @@ -31,3 +37,9 @@ runs the tests they do not endup with an unexpected git status. If you have any cases where you expect to have git report dirty, please add `--allow-dirty` to the command call to disable this check. + +## Checks system dependencies using bindep + +If a `bindep.txt` config file is found, tox will run `bindep test` to +check if dependencies, including test ones, are present. There is no need to +install bindep your self. diff --git a/pyproject.toml b/pyproject.toml index d12f840..f592853 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ ] keywords = ["git", "tox", "tox-plugin"] dependencies = [ + "bindep>2.8.1", "gitpython", "packaging", "tox", @@ -66,6 +67,7 @@ omit = [".tox/*/lib/python*/site-packages/*"] include = ["src/*"] fail_under = 100.0 skip_covered = true +show_missing = true [tool.coverage.paths] source = ["src"] diff --git a/src/tox_extra/bindep.py b/src/tox_extra/bindep.py new file mode 100644 index 0000000..376bb21 --- /dev/null +++ b/src/tox_extra/bindep.py @@ -0,0 +1,50 @@ +"""Bindep check feature implementations.""" + +from __future__ import print_function + +import os +import subprocess +import sys + + +def check_bindep() -> None: + """Check bindeps requirements or exit.""" + if os.path.isfile("bindep.txt"): + # as 'bindep --profiles' does not show user defined profiles like 'test' + # it makes no sense to list them. + cmd = [sys.executable, "-m", "bindep", "-b", "test"] + # # determine profiles + # result = subprocess.run( + # [sys.executable, "-m", "bindep", "--profiles"], + # check=False, + # universal_newlines=True, + # stdout=subprocess.PIPE, + # ) + # if result.returncode: + # print("Bindep failed to list profiles: %s", result.stdout) + # sys.exit(result.returncode) + # lines = result.stdout.splitlines() + # try: + # profiles = lines[lines.index("Configuration profiles:") + 1 :] + # if "test" in profiles: + # cmd.append("test") + # except ValueError: + # pass + + result = subprocess.run( + cmd, + check=False, + universal_newlines=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + if result.returncode: + print( + f"Running '{' '.join(cmd)}' returned {result.returncode}, " + "likely missing system dependencies." + ) + if result.stdout: + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode) diff --git a/src/tox_extra/hooks.py b/src/tox_extra/hooks.py index 633e1df..092d260 100644 --- a/src/tox_extra/hooks.py +++ b/src/tox_extra/hooks.py @@ -3,7 +3,7 @@ import os import sys from argparse import ArgumentParser -from typing import List +from typing import Any, List import git from tox.execute import Outcome @@ -11,6 +11,8 @@ from tox.tox_env.api import ToxEnv from tox.tox_env.errors import Fail +from tox_extra.bindep import check_bindep + MSG_GIT_DIRTY = ( "Git reported dirty status. " "Git should never report dirty status at the end of " @@ -49,6 +51,13 @@ def tox_add_option(parser: ArgumentParser) -> None: ) +@impl +# pylint: disable=unused-argument +def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str) -> None: + """Runs just before installing package.""" + check_bindep() + + @impl # pylint: disable=unused-argument def tox_after_run_commands( diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e7241b5 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,18 @@ +"""Tests.""" + +import functools +import os + + +def preserve_cwd(function): + """Decorator for restoring cwd.""" + + @functools.wraps(function) + def decorator(*args, **kwargs): + cwd = os.getcwd() + try: + return function(*args, **kwargs) + finally: + os.chdir(cwd) + + return decorator diff --git a/tests/fixtures/bindep_0/bindep.txt b/tests/fixtures/bindep_0/bindep.txt new file mode 100644 index 0000000..f7ab61f --- /dev/null +++ b/tests/fixtures/bindep_0/bindep.txt @@ -0,0 +1 @@ +this-package-is-missing diff --git a/tests/fixtures/bindep_0/tox.ini b/tests/fixtures/bindep_0/tox.ini new file mode 100644 index 0000000..5e7a458 --- /dev/null +++ b/tests/fixtures/bindep_0/tox.ini @@ -0,0 +1,2 @@ +[tox] +skipsdist = true diff --git a/tests/fixtures/bindep_1/bindep.txt b/tests/fixtures/bindep_1/bindep.txt new file mode 100644 index 0000000..fbee83c --- /dev/null +++ b/tests/fixtures/bindep_1/bindep.txt @@ -0,0 +1 @@ +bash [test] diff --git a/tests/fixtures/bindep_1/tox.ini b/tests/fixtures/bindep_1/tox.ini new file mode 100644 index 0000000..5e7a458 --- /dev/null +++ b/tests/fixtures/bindep_1/tox.ini @@ -0,0 +1,2 @@ +[tox] +skipsdist = true diff --git a/tests/fixtures/bindep_2/bindep.txt b/tests/fixtures/bindep_2/bindep.txt new file mode 100644 index 0000000..731ddce --- /dev/null +++ b/tests/fixtures/bindep_2/bindep.txt @@ -0,0 +1 @@ +GARBAGE DATA diff --git a/tests/fixtures/bindep_2/tox.ini b/tests/fixtures/bindep_2/tox.ini new file mode 100644 index 0000000..5e7a458 --- /dev/null +++ b/tests/fixtures/bindep_2/tox.ini @@ -0,0 +1,2 @@ +[tox] +skipsdist = true diff --git a/tests/fixtures/bindep_3/bindep.txt b/tests/fixtures/bindep_3/bindep.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_bindep.py b/tests/test_bindep.py new file mode 100644 index 0000000..35e5df6 --- /dev/null +++ b/tests/test_bindep.py @@ -0,0 +1,26 @@ +"""Unit tests.""" + +import os +from runpy import run_module + +import pytest + +from . import preserve_cwd + + +@preserve_cwd +@pytest.mark.parametrize( + ("folder", "expected_rc"), + ( + pytest.param("tests/fixtures/bindep_0", 1, id="0"), + pytest.param("tests/fixtures/bindep_1", 0, id="1"), + pytest.param("tests/fixtures/bindep_2", 1, id="2"), + ), +) +def test_bindep(folder: str, expected_rc: int) -> None: + """Tests that running tox with a bindep file that is missing deps fails.""" + os.chdir(folder) + with pytest.raises(SystemExit) as exc: + run_module("tox", run_name="__main__") + assert exc.type == SystemExit + assert exc.value.code == expected_rc diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 7995a71..47adeeb 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,6 +1,5 @@ """Hosts tests for the plugin.""" -import functools import os from pathlib import Path from runpy import run_module @@ -8,26 +7,14 @@ import pytest +from . import preserve_cwd + TOX_SAMPLE = """ [tox] skipsdist = true """ -def preserve_cwd(function): - """Decorator for restoring cwd.""" - - @functools.wraps(function) - def decorator(*args, **kwargs): - cwd = os.getcwd() - try: - return function(*args, **kwargs) - finally: - os.chdir(cwd) - - return decorator - - @preserve_cwd def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: """Validated that it fails when drity.""" diff --git a/tox.ini b/tox.ini index 11f5471..ead6dae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] minversion = 4.0.2 -skipsdist = true ignore_path = tests envlist = lint @@ -10,9 +9,12 @@ envlist = [testenv] usedevelop = true +description = + Run tests + devel: using devel branch of tox (unreleased) skip_install = false +extras = test deps = - -e ".[test]" devel: tox @ git+https://github.com/tox-dev/tox.git@main passenv = SSH_AUTH_SOCK @@ -25,14 +27,16 @@ setenv = GIT_COMMITTER_EMAIL=noreply@example.com PIP_DISABLE_PIP_VERSION_CHECK = 1 commands = - coverage run -m pytest {posargs} - sh -c "coverage combine -q --data-file={env:COVERAGE_FILE} {env:COVERAGE_FILE}.* && coverage xml && coverage report" + python -m coverage run -m pytest {posargs} + sh -c "python -m coverage combine -q --data-file={env:COVERAGE_FILE} {env:COVERAGE_FILE}.* && coverage report && coverage xml" allowlist_externals = sh rm +package = editable [testenv:lint] skip_install = true +description = Run linting deps = pre_commit commands =