Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better base python version conflict detection #983

Merged
merged 1 commit into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/908.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
instead of assuming the Python version from the base python name ask the interpreter to reveal the version for the ``ignore_basepython_conflict`` flag - by :user:`gaborbernat`
20 changes: 13 additions & 7 deletions doc/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,19 @@ and will first lookup global tox settings in this section:

.. versionadded:: 3.1.0

If ``True``, :confval:`basepython` settings that conflict with the Python
variant for environments using default factors, such as ``py27`` or
``py35``, will be ignored. This allows you to configure
:confval:`basepython` in the global testenv without affecting these
factors. If ``False``, the default, a warning will be emitted if a conflict
is identified. In a future version of tox, this warning will become an
error.
tox allows setting the python version for an environment via the :confval:`basepython`
setting. If that's not set tox can set a default value from the environment name (
e.g. ``py37`` implies Python 3.7). Matching up the python version with the environment
name has became expected at this point, leading to surprises when some configs don't
do so. To help with sanity of users a warning will be emitted whenever the environment
name version does not matches up with this expectation. In a future version of tox,
this warning will become an error.

Furthermore, we allow hard enforcing this rule (and bypassing the warning) by setting
this flag to ``True``. In such cases we ignore the :confval:`basepython` and instead
always use the base python implied from the Python name. This allows you to
configure :confval:`basepython` in the global testenv without affecting environments
that have implied base python versions.

.. confval:: requires=LIST

Expand Down
51 changes: 28 additions & 23 deletions src/tox/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import tox
from tox.constants import INFO
from tox.interpreters import Interpreters
from tox.interpreters import Interpreters, NoInterpreterInfo

hookimpl = tox.hookimpl
"""DEPRECATED - REMOVE - this is left for compatibility with plugins importing this from here.
Expand Down Expand Up @@ -532,35 +532,40 @@ def setenv(testenv_config, value):
)

def basepython_default(testenv_config, value):
"""Configure a sane interpreter for the environment.
"""either user set or proposed from the factor name

If the environment contains a default factor, this will always be the
interpreter associated with that factor overriding anything manually
set.
in both cases we check that the factor name implied python version and the resolved
python interpreter version match up; if they don't we warn, unless ignore base
python conflict is set in which case the factor name implied version if forced
"""
for factor in testenv_config.factors:
match = tox.PYTHON.PY_FACTORS_RE.match(factor)
if match:
base_exe = tox.PYTHON.PY_FACTORS_MAP[match.group(1)]
version = ".".join(match.group(2) or "")
default = "{}{}".format(base_exe, version)

if value is None or testenv_config.config.ignore_basepython_conflict:
return default

if str(value) != default:
if factor in tox.PYTHON.DEFAULT_FACTORS:
implied_python = tox.PYTHON.DEFAULT_FACTORS[factor]
break
else:
implied_python, factor = None, None

if testenv_config.config.ignore_basepython_conflict and implied_python is not None:
return implied_python

proposed_python = (implied_python or sys.executable) if value is None else str(value)
if implied_python is not None and implied_python != proposed_python:
testenv_config.basepython = proposed_python
implied_version = tox.PYTHON.PY_FACTORS_RE.match(factor).group(2)
python_info_for_proposed = testenv_config.python_info
if not isinstance(python_info_for_proposed, NoInterpreterInfo):
proposed_version = "".join(
str(i) for i in python_info_for_proposed.version_info[0:2]
)
if implied_version != proposed_version:
# TODO(stephenfin): Raise an exception here in tox 4.0
warnings.warn(
"Conflicting basepython for environment '{}'; resolve conflict "
"or configure ignore_basepython_conflict".format(
testenv_config.envname, str(value), default
"conflicting basepython version (set {}, should be {}) for env '{}';"
"resolve conflict or set ignore_basepython_conflict".format(
proposed_version, implied_version, testenv_config.envname
)
)

if value is None:
return sys.executable

return str(value)
return proposed_python

parser.add_testenv_attribute(
name="basepython",
Expand Down
27 changes: 26 additions & 1 deletion tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,7 @@ def test_default_factors(self, newconfig):
assert config.basepython == "python{}.{}".format(name[2], name[3])

def test_default_factors_conflict(self, newconfig, capsys):
with pytest.warns(UserWarning, match=r"Conflicting basepython .*"):
with pytest.warns(UserWarning, match=r"conflicting basepython .*"):
config = newconfig(
"""
[testenv]
Expand All @@ -1658,6 +1658,31 @@ def test_default_factors_conflict(self, newconfig, capsys):
envconfig = config.envconfigs["py27"]
assert envconfig.basepython == "python3"

def test_default_factors_conflict_lying_name(
self, newconfig, capsys, tmpdir, recwarn, monkeypatch
):
# we first need to create a lying Python here, let's mock out here
from tox.interpreters import Interpreters

def get_executable(self, envconfig):
return sys.executable

monkeypatch.setattr(Interpreters, "get_executable", get_executable)

major, minor = sys.version_info[0:2]
config = newconfig(
"""
[testenv:py{0}{1}]
basepython=python{0}.{2}
commands = python --version
""".format(
major, minor, minor - 1
)
)
env_config = config.envconfigs["py{}{}".format(major, minor)]
assert env_config.basepython == "python{}.{}".format(major, minor - 1)
assert not recwarn.list, "\n".join(repr(i.message) for i in recwarn.list)

def test_default_factors_conflict_ignore(self, newconfig, capsys):
with pytest.warns(None) as record:
config = newconfig(
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ commands = echo {posargs}
[flake8]
max-complexity = 22
max-line-length = 99
ignore = E203, W503
ignore = E203, W503, C901

[coverage:run]
branch = true
Expand Down