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

Un-hardcode the supported factors again #1377

Merged
merged 1 commit into from
Jul 27, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/1374.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for minor versions with multiple digits ``tox -e py310` works for ``python3.10`` - by :user:`asottile`
1 change: 1 addition & 0 deletions docs/changelog/1377.fix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix regression failing to detect future and past ``py##`` factors - by :user:`asottile`
48 changes: 26 additions & 22 deletions src/tox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@
Import hookimpl directly from tox instead.
"""

default_factors = tox.PYTHON.DEFAULT_FACTORS
"""DEPRECATED MOVE - please update to new location."""

WITHIN_PROVISION = os.environ.get(str("TOX_PROVISION")) == "1"


Expand Down Expand Up @@ -549,35 +546,42 @@ def basepython_default(testenv_config, value):
python conflict is set in which case the factor name implied version if forced
"""
for factor in testenv_config.factors:
if factor in tox.PYTHON.DEFAULT_FACTORS:
implied_python = tox.PYTHON.DEFAULT_FACTORS[factor]
match = tox.PYTHON.PY_FACTORS_RE.match(factor)
if match:
base_exe = {"py": "python"}.get(match.group(1), match.group(1))
version_s = match.group(2)
if not version_s:
version_info = ()
elif len(version_s) == 1:
version_info = (version_s,)
else:
version_info = (version_s[0], version_s[1:])
implied_version = ".".join(version_info)
implied_python = "{}{}".format(base_exe, implied_version)
break
else:
implied_python, factor = None, None
implied_python, version_info, implied_version = 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
match = tox.PYTHON.PY_FACTORS_RE.match(factor)
implied_version = match.group(2) if match else None
if implied_version is not None:
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]
)
# '27'.startswith('2') or '27'.startswith('27')
if not proposed_version.startswith(implied_version):
# TODO(stephenfin): Raise an exception here in tox 4.0
warnings.warn(
"conflicting basepython version (set {}, should be {}) for env '{}';"
"resolve conflict or set ignore_basepython_conflict".format(
proposed_version, implied_version, testenv_config.envname
)
python_info_for_proposed = testenv_config.python_info
if not isinstance(python_info_for_proposed, NoInterpreterInfo):
proposed_version = ".".join(
str(x) for x in python_info_for_proposed.version_info[: len(version_info)]
)
if proposed_version != implied_version:
# TODO(stephenfin): Raise an exception here in tox 4.0
warnings.warn(
"conflicting basepython version (set {}, should be {}) for env '{}';"
"resolve conflict or set ignore_basepython_conflict".format(
proposed_version, implied_version, testenv_config.envname
)
)

return proposed_python

parser.add_testenv_attribute(
Expand Down
31 changes: 3 additions & 28 deletions src/tox/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,11 @@
_THIS_FILE = os.path.realpath(os.path.abspath(__file__))


def _construct_default_factors(cpython_versions, pypy_versions, other_interpreters):
default_factors = {"py": sys.executable, "py2": "python2", "py3": "python3"}
default_factors.update(
{
"py{}{}".format(major, minor): "python{}.{}".format(major, minor)
for major, minor in cpython_versions
}
)
default_factors.update({exc: exc for exc in ["pypy", "pypy2", "pypy3"]})
default_factors.update(
{
"pypy{}{}".format(major, minor): "pypy{}.{}".format(major, minor)
for major, minor in pypy_versions
}
)
default_factors.update({interpreter: interpreter for interpreter in other_interpreters})
return default_factors


class PYTHON:
PY_FACTORS_RE = re.compile("^(?!py$)(py|pypy|jython)([2-9][0-9]?)?$")
CPYTHON_VERSION_TUPLES = [(2, 7), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8)]
PYPY_VERSION_TUPLES = [(2, 7), (3, 5)]
OTHER_PYTHON_INTERPRETERS = ["jython"]
DEFAULT_FACTORS = _construct_default_factors(
CPYTHON_VERSION_TUPLES, PYPY_VERSION_TUPLES, OTHER_PYTHON_INTERPRETERS
)
CURRENT_RELEASE_ENV = "py36"
PY_FACTORS_RE = re.compile("^(?!py$)(py|pypy|jython)([2-9][0-9]?[0-9]?)?$")
CURRENT_RELEASE_ENV = "py37"
"""Should hold currently released py -> for easy updating"""
QUICKSTART_PY_ENVS = ["py27", "py34", "py35", CURRENT_RELEASE_ENV, "pypy", "jython"]
QUICKSTART_PY_ENVS = ["py27", "py35", "py36", CURRENT_RELEASE_ENV, "pypy", "jython"]
"""For choices in tox-quickstart"""


Expand Down
34 changes: 16 additions & 18 deletions tests/unit/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2194,26 +2194,24 @@ def test_no_implicit_venv_from_cli_with_envlist(self, newconfig):
assert "typo-factor" not in config.envconfigs

def test_correct_basepython_chosen_from_default_factors(self, newconfig):
envlist = list(tox.PYTHON.DEFAULT_FACTORS.keys())
config = newconfig([], "[tox]\nenvlist={}".format(", ".join(envlist)))
assert config.envlist == envlist
envs = {
"py": sys.executable,
"py2": "python2",
"py3": "python3",
"py27": "python2.7",
"py36": "python3.6",
"py310": "python3.10",
"pypy": "pypy",
"pypy2": "pypy2",
"pypy3": "pypy3",
"pypy36": "pypy3.6",
"jython": "jython",
}
config = newconfig([], "[tox]\nenvlist={}".format(", ".join(envs)))
assert set(config.envlist) == set(envs)
for name in config.envlist:
basepython = config.envconfigs[name].basepython
if name == "jython":
assert basepython == "jython"
elif name in ("pypy2", "pypy3"):
assert basepython == "pypy" + name[-1]
elif name in ("py2", "py3"):
assert basepython == "python" + name[-1]
elif name == "pypy":
assert basepython == name
elif name == "py":
assert "python" in basepython or "pypy" in basepython
elif "pypy" in name:
assert basepython == "pypy{}.{}".format(name[-2], name[-1])
else:
assert name.startswith("py")
assert basepython == "python{}.{}".format(name[2], name[3])
assert basepython == envs[name]

def test_envlist_expansion(self, newconfig):
inisource = """
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/interpreters/test_interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def assert_version_in_output(exe, version):

p = tox_get_python_executable(envconfig)
assert p == py.path.local(sys.executable)
for major, minor in tox.PYTHON.CPYTHON_VERSION_TUPLES:
for major, minor in [(2, 7), (3, 5), (3, 6), (3, 7), (3, 8)]:
name = "python{}.{}".format(major, minor)
if tox.INFO.IS_WIN:
pydir = "python{}{}".format(major, minor)
Expand Down
25 changes: 15 additions & 10 deletions tests/unit/test_z_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,26 +164,31 @@ def test_unknown_interpreter_and_env(cmd, initproj):
"interp123-0.5",
filedefs={
"tests": {"test_hello.py": "def test_hello(): pass"},
"tox.ini": """
[testenv:python]
basepython=xyz_unknown_interpreter
[testenv]
changedir=tests
skip_install = true
""",
"tox.ini": """\
[testenv:python]
basepython=xyz_unknown_interpreter
[testenv]
changedir=tests
skip_install = true
""",
},
)
result = cmd()
result.assert_fail()
assert any(
"ERROR: InterpreterNotFound: xyz_unknown_interpreter" == l for l in result.outlines
), result.outlines
assert "ERROR: InterpreterNotFound: xyz_unknown_interpreter" in result.outlines

result = cmd("-exyz")
result.assert_fail()
assert result.out == "ERROR: unknown environment 'xyz'\n"


def test_unknown_interpreter_factor(cmd, initproj):
initproj("py21", filedefs={"tox.ini": "[testenv]\nskip_install=true"})
result = cmd("-e", "py21")
result.assert_fail()
assert "ERROR: InterpreterNotFound: python2.1" in result.outlines


def test_unknown_interpreter(cmd, initproj):
initproj(
"interp123-0.5",
Expand Down