Skip to content

Commit c790c60

Browse files
authored
Fix various issues with missing interpreters (#2828)
fix #2811
1 parent af35384 commit c790c60

File tree

11 files changed

+80
-23
lines changed

11 files changed

+80
-23
lines changed

docs/changelog/2811.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The combination of ``usedevelop = true`` and ``--skip-missing-interpreters=false`` will no longer fail for environments
2+
that were *not* invoked - by :user:`stephenfin`.

docs/changelog/2826.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an attribute error when ``use_develop = true`` is set and an unsupported interpreter version is requested - by
2+
:user:`stephenfin`.

docs/changelog/2827.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tox returns a non-zero error code if all envs are skipped. It will now correctly do this if only a single env was
2+
requested and this was skipped - by :user:`stephenfin`.

docs/upgrading.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ moving to the newly added ``py_impl`` and ``py_dot_ver`` variables, for example:
8888
8989
deps = -r{py_impl}{py_dot_ver}-req.txt
9090
91+
Failure when all environments are skipped
92+
-----------------------------------------
93+
94+
A run that results in all environments being skipped will no longer result in success. Instead, a failure will be
95+
reported. For example, consider a host that does not support Python 3.5:
96+
97+
.. code-block:: bash
98+
99+
tox run --skip-missing-interpreters=true -e py35
100+
101+
This will now result in a failure.
102+
91103
Substitutions removed
92104
---------------------
93105

@@ -134,7 +146,6 @@ Re-use of environments
134146
[testenv:b]
135147
deps = pytest<7
136148
137-
138149
CLI command compatibility
139150
-------------------------
140151

src/tox/session/cmd/run/common.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ def _print(color_: int, message: str) -> None:
187187
_print(Fore.GREEN, f" congratulations :) ({duration:.2f} seconds)")
188188
return Outcome.OK
189189
_print(Fore.RED, f" evaluation failed :( ({duration:.2f} seconds)")
190-
return runs[0].code if len(runs) == 1 else -1
190+
if len(runs) == 1:
191+
return runs[0].code if not runs[0].skipped else -1
192+
return -1
191193

192194

193195
def _get_outcome_message(run: ToxEnvRunResult) -> tuple[str, int]:

src/tox/tox_env/python/api.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,27 +231,29 @@ def python_cache(self) -> dict[str, Any]:
231231
@property
232232
def base_python(self) -> PythonInfo:
233233
"""Resolve base python"""
234+
base_pythons: list[str] = self.conf["base_python"]
235+
234236
if self._base_python_searched is False:
235-
base_pythons: list[str] = self.conf["base_python"]
236237
self._base_python_searched = True
237238
self._base_python = self._get_python(base_pythons)
238-
if self._base_python is None:
239-
if self.core["skip_missing_interpreters"]:
240-
raise Skip(f"could not find python interpreter with spec(s): {', '.join(base_pythons)}")
241-
raise NoInterpreter(base_pythons)
242-
if self.journal:
239+
if self._base_python is not None and self.journal:
243240
value = self._get_env_journal_python()
244241
self.journal["python"] = value
242+
243+
if self._base_python is None:
244+
if self.core["skip_missing_interpreters"]:
245+
raise Skip(f"could not find python interpreter with spec(s): {', '.join(base_pythons)}")
246+
raise NoInterpreter(base_pythons)
247+
245248
return cast(PythonInfo, self._base_python)
246249

247250
def _get_env_journal_python(self) -> dict[str, Any]:
248-
assert self._base_python is not None
249251
return {
250-
"implementation": self._base_python.implementation,
252+
"implementation": self.base_python.implementation,
251253
"version_info": tuple(self.base_python.version_info),
252-
"version": self._base_python.version,
253-
"is_64": self._base_python.is_64,
254-
"sysplatform": self._base_python.platform,
254+
"version": self.base_python.version,
255+
"is_64": self.base_python.is_64,
256+
"sysplatform": self.base_python.platform,
255257
"extra_version_info": None,
256258
}
257259

src/tox/tox_env/python/package.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ..errors import Skip
1515
from ..package import Package, PackageToxEnv, PathPackage
1616
from ..runner import RunToxEnv
17-
from .api import Python
17+
from .api import NoInterpreter, Python
1818
from .pip.req_file import PythonDeps
1919

2020
if TYPE_CHECKING:
@@ -87,7 +87,11 @@ def default_wheel_tag(conf: Config, env_name: str | None) -> str: # noqa: U100
8787
# python only code are often compatible at major level (unless universal wheel in which case both 2/3)
8888
# c-extension codes are trickier, but as of today both poetry/setuptools uses pypa/wheels logic
8989
# https://github.com/pypa/wheel/blob/master/src/wheel/bdist_wheel.py#L234-L280
90-
run_py = cast(Python, run_env).base_python
90+
try:
91+
run_py = cast(Python, run_env).base_python
92+
except NoInterpreter:
93+
run_py = None
94+
9195
if run_py is None:
9296
base = ",".join(run_env.conf["base_python"])
9397
raise Skip(f"could not resolve base python with {base}")

tests/session/cmd/test_depends.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ def test_depends(tox_project: ToxProjectCreator, patch_prev_py: Callable[[bool],
3434
py ~ .pkg
3535
{py} ~ .pkg
3636
{prev_py} ~ .pkg | .pkg-{impl}{prev_ver}
37-
py31 ~ .pkg | ... (could not resolve base python with py31)
37+
py31 ~ .pkg | ... (could not find python interpreter with spec(s): py31)
3838
cov2
3939
cov
4040
py ~ .pkg
4141
{py} ~ .pkg
4242
{prev_py} ~ .pkg | .pkg-{impl}{prev_ver}
43-
py31 ~ .pkg | ... (could not resolve base python with py31)
43+
py31 ~ .pkg | ... (could not find python interpreter with spec(s): py31)
4444
cov
4545
py ~ .pkg
4646
{py} ~ .pkg
4747
{prev_py} ~ .pkg | .pkg-{impl}{prev_ver}
48-
py31 ~ .pkg | ... (could not resolve base python with py31)
48+
py31 ~ .pkg | ... (could not find python interpreter with spec(s): py31)
4949
"""
5050
assert outcome.out == dedent(expected).lstrip()
5151

tests/session/cmd/test_sequential.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def test_missing_interpreter_skip_on(tox_project: ToxProjectCreator) -> None:
224224
proj = tox_project({"tox.ini": ini})
225225

226226
result = proj.run("r")
227-
result.assert_success()
227+
result.assert_failed()
228228
assert "py: SKIP" in result.out
229229

230230

@@ -367,7 +367,7 @@ def test_platform_does_not_match_run_env(tox_project: ToxProjectCreator) -> None
367367
proj = tox_project({"tox.ini": ini})
368368

369369
result = proj.run("r")
370-
result.assert_success()
370+
result.assert_failed()
371371
exp = f"py: skipped because platform {sys.platform} does not match wrong_platform"
372372
assert exp in result.out
373373

tests/tox_env/python/test_python_runner.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,39 @@ def test_extras_are_normalized(
128128
("config", "cli", "expected"),
129129
[("false", "true", True), ("true", "false", False), ("false", "config", False), ("true", "config", True)],
130130
)
131-
def test_config_skip_missing_interpreters(tox_project: ToxProjectCreator, config: str, cli: str, expected: str) -> None:
131+
def test_config_skip_missing_interpreters(
132+
tox_project: ToxProjectCreator,
133+
config: str,
134+
cli: str,
135+
expected: bool,
136+
) -> None:
132137
py_ver = ".".join(str(i) for i in sys.version_info[0:2])
133138
project = tox_project({"tox.ini": f"[tox]\nenvlist=py4,py{py_ver}\nskip_missing_interpreters={config}"})
134-
result = project.run("--skip-missing-interpreters", cli)
135-
assert result.code == 0 if expected else 1
139+
result = project.run(f"--skip-missing-interpreters={cli}")
140+
assert result.code == (0 if expected else -1)
141+
142+
143+
@pytest.mark.parametrize(
144+
("skip", "env", "retcode"),
145+
[
146+
("true", f"py{''.join(str(i) for i in sys.version_info[0:2])}", 0),
147+
("false", f"py{''.join(str(i) for i in sys.version_info[0:2])}", 0),
148+
("true", "py31", -1),
149+
("false", "py31", 1),
150+
("true", None, 0),
151+
("false", None, -1),
152+
],
153+
)
154+
def test_skip_missing_interpreters_specified_env(
155+
tox_project: ToxProjectCreator,
156+
skip: str,
157+
env: str | None,
158+
retcode: int,
159+
) -> None:
160+
py_ver = "".join(str(i) for i in sys.version_info[0:2])
161+
project = tox_project({"tox.ini": f"[tox]\nenvlist=py31,py{py_ver}\n[testenv]\nusedevelop=true"})
162+
args = [f"--skip-missing-interpreters={skip}"]
163+
if env:
164+
args += ["-e", env]
165+
result = project.run(*args)
166+
assert result.code == retcode

0 commit comments

Comments
 (0)