summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.pre-commit-config.yaml2
-rw-r--r--docs/changelog/1843.bugfix.rst2
-rw-r--r--docs/changelog/1843.feature.rst1
-rw-r--r--src/tox/session/cmd/run/single.py68
-rw-r--r--src/tox/tox_env/runner.py8
-rw-r--r--tests/demo_pkg_inline/build.py13
-rw-r--r--tests/demo_pkg_setuptools/demo_pkg_setuptools/__init__.py2
-rw-r--r--tests/session/cmd/test_sequential.py66
-rw-r--r--tox.ini2
9 files changed, 124 insertions, 40 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d388dd29..326682d4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -42,7 +42,7 @@ repos:
- --min-py3-version
- "3.6"
- repo: https://github.com/asottile/blacken-docs
- rev: v1.9.1
+ rev: v1.9.2
hooks:
- id: blacken-docs
additional_dependencies:
diff --git a/docs/changelog/1843.bugfix.rst b/docs/changelog/1843.bugfix.rst
new file mode 100644
index 00000000..e5bd4507
--- /dev/null
+++ b/docs/changelog/1843.bugfix.rst
@@ -0,0 +1,2 @@
+Fix handling of ``commands_pre``/``commands``/``commands_post`` to be in line with tox 3 (returned incorrect exit codes
+and post was not always executed) - by :user:`gaborbernat`.
diff --git a/docs/changelog/1843.feature.rst b/docs/changelog/1843.feature.rst
new file mode 100644
index 00000000..e2850004
--- /dev/null
+++ b/docs/changelog/1843.feature.rst
@@ -0,0 +1 @@
+Add support for the ``ignore_errors`` settings in tox test environments - by :user:`gaborbernat`.
diff --git a/src/tox/session/cmd/run/single.py b/src/tox/session/cmd/run/single.py
index 8f0881e2..4b89b7f7 100644
--- a/src/tox/session/cmd/run/single.py
+++ b/src/tox/session/cmd/run/single.py
@@ -3,11 +3,11 @@ Defines how to run a single tox environment.
"""
import logging
import time
-from typing import List, NamedTuple, Tuple, cast
+from pathlib import Path
+from typing import List, NamedTuple, Tuple
from tox.config.types import Command
from tox.execute.api import Outcome, StdinSource
-from tox.tox_env.api import ToxEnv
from tox.tox_env.errors import Fail, Skip
from tox.tox_env.python.virtual_env.package.api import ToxBackendFailed
from tox.tox_env.runner import RunToxEnv
@@ -32,7 +32,7 @@ def run_one(tox_env: RunToxEnv, recreate: bool, no_test: bool, suspend_display:
return ToxEnvRunResult(name, skipped, code, outcomes, duration)
-def _evaluate(tox_env: ToxEnv, recreate: bool, no_test: bool) -> Tuple[bool, int, List[Outcome]]:
+def _evaluate(tox_env: RunToxEnv, recreate: bool, no_test: bool) -> Tuple[bool, int, List[Outcome]]:
skipped = False
code: int = 0
outcomes: List[Outcome] = []
@@ -59,28 +59,48 @@ def _evaluate(tox_env: ToxEnv, recreate: bool, no_test: bool) -> Tuple[bool, int
return skipped, code, outcomes
-def run_commands(tox_env: ToxEnv, no_test: bool) -> Tuple[int, List[Outcome]]:
- status = Outcome.OK # assume all good
+def run_commands(tox_env: RunToxEnv, no_test: bool) -> Tuple[int, List[Outcome]]:
outcomes: List[Outcome] = []
- if no_test is False:
- keys = ("commands_pre", "commands", "commands_post")
- for key in keys:
- for at, cmd in enumerate(cast(List[Command], tox_env.conf[key])):
- current_outcome = tox_env.execute(
- cmd.args,
- cwd=tox_env.conf["change_dir"],
- stdin=StdinSource.user_only(),
- show=True,
- run_id=f"{key}[{at}]",
- )
- outcomes.append(current_outcome)
- if cmd.ignore_exit_code:
- continue
- try:
- current_outcome.assert_success()
- except SystemExit as exception:
- return exception.code, outcomes
- return status, outcomes
+ if no_test:
+ status_pre, status_main, status_post = Outcome.OK, Outcome.OK, Outcome.OK
+ else:
+ chdir: Path = tox_env.conf["change_dir"]
+ ignore_errors: bool = tox_env.conf["ignore_errors"]
+ try:
+ status_pre = run_command_set(tox_env, "commands_pre", chdir, ignore_errors, outcomes)
+ if status_pre == Outcome.OK or ignore_errors:
+ status_main = run_command_set(tox_env, "commands", chdir, ignore_errors, outcomes)
+ else:
+ status_main = Outcome.OK
+ finally:
+ status_post = run_command_set(tox_env, "commands_post", chdir, ignore_errors, outcomes)
+ exit_code = status_pre or status_main or status_post # first non-success
+ return exit_code, outcomes
+
+
+def run_command_set(tox_env: RunToxEnv, key: str, cwd: Path, ignore_errors: bool, outcomes: List[Outcome]) -> int:
+ exit_code = Outcome.OK
+ command_set: List[Command] = tox_env.conf[key]
+ for at, cmd in enumerate(command_set):
+ current_outcome = tox_env.execute(
+ cmd.args,
+ cwd=cwd,
+ stdin=StdinSource.user_only(),
+ show=True,
+ run_id=f"{key}[{at}]",
+ )
+ outcomes.append(current_outcome)
+ try:
+ current_outcome.assert_success()
+ except SystemExit as exception:
+ if cmd.ignore_exit_code:
+ continue
+ if ignore_errors:
+ if exit_code == Outcome.OK:
+ exit_code = exception.code # ignore errors continues ahead but saves the exit code
+ continue
+ return exception.code
+ return exit_code
__all__ = (
diff --git a/src/tox/tox_env/runner.py b/src/tox/tox_env/runner.py
index 1a44d7c0..146e395f 100644
--- a/src/tox/tox_env/runner.py
+++ b/src/tox/tox_env/runner.py
@@ -59,7 +59,13 @@ class RunToxEnv(ToxEnv, ABC):
keys=["change_dir", "changedir"],
of_type=Path,
default=lambda conf, name: cast(Path, conf.core["tox_root"]),
- desc="Change to this working directory when executing the test command.",
+ desc="change to this working directory when executing the test command",
+ )
+ self.conf.add_config(
+ keys=["ignore_errors"],
+ of_type=bool,
+ default=False,
+ desc="when executing the commands keep going even if a sub-command exits with non-zero exit code",
)
self.has_package = self.add_package_conf()
diff --git a/tests/demo_pkg_inline/build.py b/tests/demo_pkg_inline/build.py
index 70663632..7476d88f 100644
--- a/tests/demo_pkg_inline/build.py
+++ b/tests/demo_pkg_inline/build.py
@@ -2,6 +2,7 @@ import sys
import tarfile
from pathlib import Path
from textwrap import dedent
+from typing import Dict, List, Optional
from zipfile import ZipFile
name = "demo_pkg_inline"
@@ -42,7 +43,11 @@ content = {
}
-def build_wheel(wheel_directory, metadata_directory=None, config_settings=None):
+def build_wheel(
+ wheel_directory: str,
+ metadata_directory: Optional[str] = None,
+ config_settings: Optional[Dict[str, str]] = None,
+) -> str:
base_name = f"{name}-{version}-py{sys.version_info.major}-none-any.whl"
path = Path(wheel_directory) / base_name
with ZipFile(str(path), "w") as zip_file_handler:
@@ -52,11 +57,11 @@ def build_wheel(wheel_directory, metadata_directory=None, config_settings=None):
return base_name
-def get_requires_for_build_wheel(config_settings):
+def get_requires_for_build_wheel(config_settings: Optional[Dict[str, str]] = None) -> List[str]:
return [] # pragma: no cover # only executed in non-host pythons
-def build_sdist(sdist_directory, config_settings=None):
+def build_sdist(sdist_directory: str, config_settings: Optional[Dict[str, str]] = None) -> str:
result = f"{name}-{version}.tar.gz"
with tarfile.open(str(Path(sdist_directory) / result), "w:gz") as tar:
root = Path(__file__).parent
@@ -65,5 +70,5 @@ def build_sdist(sdist_directory, config_settings=None):
return result
-def get_requires_for_build_sdist(config_settings):
+def get_requires_for_build_sdist(config_settings: Optional[Dict[str, str]] = None) -> List[str]:
return [] # pragma: no cover # only executed in non-host pythons
diff --git a/tests/demo_pkg_setuptools/demo_pkg_setuptools/__init__.py b/tests/demo_pkg_setuptools/demo_pkg_setuptools/__init__.py
index 694740b1..3d596bf8 100644
--- a/tests/demo_pkg_setuptools/demo_pkg_setuptools/__init__.py
+++ b/tests/demo_pkg_setuptools/demo_pkg_setuptools/__init__.py
@@ -1,2 +1,2 @@
-def do():
+def do(): # type: () -> None
print("greetings from demo_pkg_setuptools")
diff --git a/tests/session/cmd/test_sequential.py b/tests/session/cmd/test_sequential.py
index a14e2ff3..e9b73a71 100644
--- a/tests/session/cmd/test_sequential.py
+++ b/tests/session/cmd/test_sequential.py
@@ -161,8 +161,8 @@ def test_recreate_package(tox_project: ToxProjectCreator, demo_pkg_inline: Path)
def test_package_deps_change(tox_project: ToxProjectCreator, demo_pkg_inline: Path) -> None:
toml = (demo_pkg_inline / "pyproject.toml").read_text()
build = (demo_pkg_inline / "build.py").read_text()
- ini = "[testenv]\npackage=wheel\ncommands=python -c 'from demo_pkg_inline import do; do()'"
- proj = tox_project({"tox.ini": ini, "pyproject.toml": toml, "build.py": build})
+ proj = tox_project({"tox.ini": "[testenv]\npackage=wheel", "pyproject.toml": toml, "build.py": build})
+ proj.patch_execute(lambda r: 0 if "install" in r.run_id else None)
result_first = proj.run("r")
result_first.assert_success()
@@ -170,12 +170,7 @@ def test_package_deps_change(tox_project: ToxProjectCreator, demo_pkg_inline: Pa
# new deps are picked up
(proj.path / "pyproject.toml").write_text(toml.replace("requires = []", 'requires = ["wheel"]'))
- (proj.path / "build.py").write_text(
- build.replace(
- "def get_requires_for_build_wheel(config_settings):\n return []",
- "def get_requires_for_build_wheel(config_settings):\n return ['setuptools']",
- )
- )
+ (proj.path / "build.py").write_text(build.replace("return []", "return ['setuptools']"))
result_rerun = proj.run("r")
result_rerun.assert_success()
@@ -389,3 +384,58 @@ def test_skip_develop_mode(tox_project: ToxProjectCreator, demo_pkg_setuptools:
(".pkg", "_exit"),
]
assert calls == expected
+
+
+def _c(code: int) -> str:
+ return f"python -c 'raise SystemExit({code})'"
+
+
+def test_commands_pre_fail_post_runs(tox_project: ToxProjectCreator) -> None:
+ ini = f"[testenv]\npackage=skip\ncommands_pre={_c(8)}\ncommands={_c(0)}\ncommands_post={_c(9)}"
+ proj = tox_project({"tox.ini": ini})
+ result = proj.run()
+ result.assert_failed(code=8)
+ assert "commands_pre[0]" in result.out
+ assert "commands[0]" not in result.out
+ assert "commands_post[0]" in result.out
+
+
+def test_commands_pre_pass_post_runs_main_fails(tox_project: ToxProjectCreator) -> None:
+ ini = f"[testenv]\npackage=skip\ncommands_pre={_c(0)}\ncommands={_c(8)}\ncommands_post={_c(9)}"
+ proj = tox_project({"tox.ini": ini})
+ result = proj.run()
+ result.assert_failed(code=8)
+ assert "commands_pre[0]" in result.out
+ assert "commands[0]" in result.out
+ assert "commands_post[0]" in result.out
+
+
+def test_commands_post_fails_exit_code(tox_project: ToxProjectCreator) -> None:
+ ini = f"[testenv]\npackage=skip\ncommands_pre={_c(0)}\ncommands={_c(0)}\ncommands_post={_c(9)}"
+ proj = tox_project({"tox.ini": ini})
+ result = proj.run()
+ result.assert_failed(code=9)
+ assert "commands_pre[0]" in result.out
+ assert "commands[0]" in result.out
+ assert "commands_post[0]" in result.out
+
+
+@pytest.mark.parametrize(
+ ["pre", "main", "post", "outcome"],
+ [
+ (0, 8, 0, 8),
+ (0, 0, 8, 8),
+ (8, 0, 0, 8),
+ ],
+)
+def test_commands_ignore_errors(tox_project: ToxProjectCreator, pre: int, main: int, post: int, outcome: int) -> None:
+ def _s(key: str, code: int) -> str:
+ return f"\ncommands{key}=\n {_c(code)}\n {'' if code == 0 else _c(code+1)}"
+
+ ini = f"[testenv]\npackage=skip\nignore_errors=True{_s('_pre', pre)}{_s('', main)}{_s('_post', post)}"
+ proj = tox_project({"tox.ini": ini})
+ result = proj.run()
+ result.assert_failed(code=outcome)
+ assert "commands_pre[0]" in result.out
+ assert "commands[0]" in result.out
+ assert "commands_post[0]" in result.out
diff --git a/tox.ini b/tox.ini
index 288778c6..bb920f55 100644
--- a/tox.ini
+++ b/tox.ini
@@ -51,7 +51,7 @@ commands =
description = run type check on code base
setenv = {tty:MYPY_FORCE_COLOR = 1}
deps =
- mypy==0.790
+ mypy==0.800
commands =
mypy src/tox
mypy tests