summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2021-02-15 07:44:21 +0000
committerBernát Gábor <gaborjbernat@gmail.com>2021-02-15 07:52:20 +0000
commit098f047fae45362ef3f66589d31b5e40cd185185 (patch)
tree90e2dc4f8193fa9480b74c8a6cb12253c99da723
parentd13f172b37678422bfcdaaa9610b41ac633d5590 (diff)
downloadtox-git-098f047fae45362ef3f66589d31b5e40cd185185.tar.gz
Fix tox is not exiting because package env thread holding it up
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
-rw-r--r--docs/changelog/1915.bugfix.rst2
-rw-r--r--docs/changelog/1915.feature.rst3
-rw-r--r--src/tox/execute/api.py25
-rw-r--r--src/tox/execute/local_sub_process/__init__.py6
-rw-r--r--src/tox/run.py7
-rw-r--r--src/tox/session/cmd/run/common.py12
-rw-r--r--src/tox/session/state.py3
-rw-r--r--src/tox/tox_env/python/virtual_env/package/api.py21
-rw-r--r--tests/execute/local_subprocess/test_local_subprocess.py5
-rw-r--r--tox.ini4
10 files changed, 63 insertions, 25 deletions
diff --git a/docs/changelog/1915.bugfix.rst b/docs/changelog/1915.bugfix.rst
new file mode 100644
index 00000000..d3e137a9
--- /dev/null
+++ b/docs/changelog/1915.bugfix.rst
@@ -0,0 +1,2 @@
+Fix a bug that caused tox to never finish when pulling configuration from a tox run environment that was never executed
+- by :user:`gaborbernat`.
diff --git a/docs/changelog/1915.feature.rst b/docs/changelog/1915.feature.rst
new file mode 100644
index 00000000..562c9dfe
--- /dev/null
+++ b/docs/changelog/1915.feature.rst
@@ -0,0 +1,3 @@
+The ``_TOX_SHOW_THREAD`` environment variable can be used to print alive threads when tox exists (useful to debug
+when tox hangs because of some non-finished thread) and also now prints the pid of the local subprocess when reporting
+the outcome of a execution - by :user:`gaborbernat`.
diff --git a/src/tox/execute/api.py b/src/tox/execute/api.py
index 338494e9..aa996ef2 100644
--- a/src/tox/execute/api.py
+++ b/src/tox/execute/api.py
@@ -7,7 +7,7 @@ import time
from abc import ABC, abstractmethod
from contextlib import contextmanager
from types import TracebackType
-from typing import Callable, Iterator, NoReturn, Optional, Sequence, Tuple, Type
+from typing import Any, Callable, Dict, Iterator, NoReturn, Optional, Sequence, Tuple, Type
from colorama import Fore
@@ -57,6 +57,10 @@ class ExecuteStatus(ABC):
def err(self) -> bytearray:
return self._err.content
+ @property
+ def metadata(self) -> Dict[str, Any]:
+ return {}
+
class Execute(ABC):
"""Abstract API for execution of a tox environment"""
@@ -79,7 +83,9 @@ class Execute(ABC):
exit_code = status.exit_code
finally:
end = time.monotonic()
- status.outcome = Outcome(request, show, exit_code, out_sync.text, err_sync.text, start, end, instance.cmd)
+ status.outcome = Outcome(
+ request, show, exit_code, out_sync.text, err_sync.text, start, end, instance.cmd, status.metadata
+ )
@abstractmethod
def build_instance(
@@ -138,6 +144,7 @@ class Outcome:
start: float,
end: float,
cmd: Sequence[str],
+ metadata: Dict[str, Any],
):
self.request = request
self.show_on_standard = show_on_standard
@@ -147,6 +154,7 @@ class Outcome:
self.start = start
self.end = end
self.cmd = cmd
+ self.metadata = metadata
def __bool__(self) -> bool:
return self.exit_code == self.OK
@@ -179,7 +187,18 @@ class Outcome:
def log_run_done(self, lvl: int) -> None:
req = self.request
- LOGGER.log(lvl, "exit %s (%.2f seconds) %s> %s", self.exit_code, self.elapsed, req.cwd, req.shell_cmd)
+ metadata = ""
+ if self.metadata:
+ metadata = f" {', '.join(f'{k}={v}' for k, v in self.metadata.items())}"
+ LOGGER.log(
+ lvl,
+ "exit %s (%.2f seconds) %s> %s%s",
+ self.exit_code,
+ self.elapsed,
+ req.cwd,
+ req.shell_cmd,
+ metadata,
+ )
@property
def elapsed(self) -> float:
diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py
index ebb5b495..ed4f6690 100644
--- a/src/tox/execute/local_sub_process/__init__.py
+++ b/src/tox/execute/local_sub_process/__init__.py
@@ -6,7 +6,7 @@ import sys
import time
from subprocess import DEVNULL, PIPE, TimeoutExpired
from types import TracebackType
-from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, Type
+from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type
from ..api import Execute, ExecuteInstance, ExecuteStatus
from ..request import ExecuteRequest, StdinSource
@@ -134,6 +134,10 @@ class LocalSubprocessExecuteStatus(ExecuteStatus):
def __repr__(self) -> str:
return f"{self.__class__.__name__}(pid={self._process.pid}, returncode={self._process.returncode!r})"
+ @property
+ def metadata(self) -> Dict[str, Any]:
+ return {"pid": self._process.pid} if self._process.pid else {}
+
class LocalSubprocessExecuteFailedStatus(ExecuteStatus):
def __init__(self, out: SyncWrite, err: SyncWrite, exit_code: Optional[int]) -> None:
diff --git a/src/tox/run.py b/src/tox/run.py
index aaeeb427..eb2222ee 100644
--- a/src/tox/run.py
+++ b/src/tox/run.py
@@ -1,5 +1,6 @@
"""Main entry point for tox."""
import logging
+import os
import sys
import time
from itertools import chain
@@ -27,6 +28,12 @@ def run(args: Optional[Sequence[str]] = None) -> None:
raise
except KeyboardInterrupt:
result = -2
+ finally:
+ if "_TOX_SHOW_THREAD" in os.environ: # pragma: no cover
+ import threading # pragma: no cover
+
+ for thread in threading.enumerate(): # pragma: no cover
+ print(thread) # pragma: no cover
raise SystemExit(result)
diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py
index d39b0279..9869cbcb 100644
--- a/src/tox/session/cmd/run/common.py
+++ b/src/tox/session/cmd/run/common.py
@@ -202,11 +202,6 @@ def execute(state: State, max_workers: Optional[int], has_spinner: bool, live: b
exit_code = report(state.options.start, ordered_results, state.options.is_colored)
if has_previous:
signal(SIGINT, previous)
- if "_TOX_SHOW_THREAD" in os.environ: # pragma: no cover
- import threading # pragma: no cover
-
- for thread in threading.enumerate(): # pragma: no cover
- print(thread) # pragma: no cover
return exit_code
@@ -299,7 +294,12 @@ def _queue_and_wait(
finally:
executor.shutdown(wait=True)
finally:
- done.set()
+ try:
+ # call teardown - configuration only environments for example could not be finished
+ for _, tox_env in state.run_envs():
+ tox_env.teardown()
+ finally:
+ done.set()
def _handle_one_run_done(result: ToxEnvRunResult, spinner: ToxSpinner, state: State, live: bool) -> None:
diff --git a/src/tox/session/state.py b/src/tox/session/state.py
index 6ca22e65..d43813c8 100644
--- a/src/tox/session/state.py
+++ b/src/tox/session/state.py
@@ -126,6 +126,9 @@ class State:
self._pkg_env[name] = packager, pkg_tox_env
return pkg_tox_env
+ def run_envs(self) -> Iterator[Tuple[str, RunToxEnv]]:
+ yield from self._run_env.items()
+
@impl
def tox_add_option(parser: "ToxParser") -> None:
diff --git a/src/tox/tox_env/python/virtual_env/package/api.py b/src/tox/tox_env/python/virtual_env/package/api.py
index e8c2e22f..60359df9 100644
--- a/src/tox/tox_env/python/virtual_env/package/api.py
+++ b/src/tox/tox_env/python/virtual_env/package/api.py
@@ -279,16 +279,17 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend):
return env
def teardown(self) -> None:
- self.ref_count.decrement()
- if self.ref_count.value == 0 and self._backend_executor is not None and self._teardown_done is False:
- self._teardown_done = True
- try:
- if self.backend_executor.is_alive:
- self._send("_exit") # try first on amicable shutdown
- except SystemExit: # if already has been interrupted ignore
- pass
- finally:
- self._backend_executor.close()
+ if not self._teardown_done:
+ self.ref_count.decrement()
+ if self.ref_count.value == 0 and self._backend_executor is not None and self._teardown_done is False:
+ self._teardown_done = True
+ try:
+ if self.backend_executor.is_alive:
+ self._send("_exit") # try first on amicable shutdown
+ except SystemExit: # if already has been interrupted ignore
+ pass
+ finally:
+ self._backend_executor.close()
@contextmanager
def _send_msg(
diff --git a/tests/execute/local_subprocess/test_local_subprocess.py b/tests/execute/local_subprocess/test_local_subprocess.py
index cb9e69f8..0f58ee1b 100644
--- a/tests/execute/local_subprocess/test_local_subprocess.py
+++ b/tests/execute/local_subprocess/test_local_subprocess.py
@@ -181,13 +181,14 @@ def test_local_execute_basic_fail(capsys: CaptureFixture, caplog: LogCaptureFixt
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelno == logging.CRITICAL
- assert record.msg == "exit %s (%.2f seconds) %s> %s"
- _code, _duration, _cwd, _cmd = record.args
+ assert record.msg == "exit %s (%.2f seconds) %s> %s%s"
+ _code, _duration, _cwd, _cmd, _metadata = record.args
assert _code == 3
assert _cwd == cwd
assert _cmd == request.shell_cmd
assert isinstance(_duration, float)
assert _duration > 0
+ assert _metadata.startswith(" pid=")
def test_command_does_not_exist(caplog: LogCaptureFixture, os_env: Dict[str, str]) -> None:
diff --git a/tox.ini b/tox.ini
index 4974ece6..a02b751f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -118,9 +118,8 @@ commands =
[testenv:release]
description = do a release, required posarg of the version number
-passenv =
- *
basepython = python3.8
+skip_install = true
deps =
gitpython>=3.1
packaging>=20.9
@@ -133,7 +132,6 @@ description = dev environment with all deps at {envdir}
usedevelop = true
deps =
{[testenv:release]deps}
- setuptools_scm>=3
extras =
docs
testing