diff --git a/docs/changelog/1374.feature.rst b/docs/changelog/1374.feature.rst new file mode 100644 index 0000000000..171911f34b --- /dev/null +++ b/docs/changelog/1374.feature.rst @@ -0,0 +1 @@ +Add support for minor versions with multiple digits ``tox -e py310` works for ``python3.10`` - by :user:`asottile` diff --git a/docs/changelog/1377.fix.rst b/docs/changelog/1377.fix.rst new file mode 100644 index 0000000000..2834b85b0a --- /dev/null +++ b/docs/changelog/1377.fix.rst @@ -0,0 +1 @@ +Fix regression failing to detect future and past ``py##`` factors - by :user:`asottile` diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py index 849b31f7e7..b5df358339 100644 --- a/src/tox/config/__init__.py +++ b/src/tox/config/__init__.py @@ -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" @@ -549,11 +546,21 @@ 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 @@ -561,23 +568,20 @@ def basepython_default(testenv_config, value): 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( diff --git a/src/tox/constants.py b/src/tox/constants.py index 8dcc86bf8a..c31f2602fe 100644 --- a/src/tox/constants.py +++ b/src/tox/constants.py @@ -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""" diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 7893d012b7..d3b17970f1 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -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 = """ diff --git a/tests/unit/interpreters/test_interpreters.py b/tests/unit/interpreters/test_interpreters.py index c713015fe7..31d67ac7ed 100644 --- a/tests/unit/interpreters/test_interpreters.py +++ b/tests/unit/interpreters/test_interpreters.py @@ -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) diff --git a/tests/unit/test_z_cmdline.py b/tests/unit/test_z_cmdline.py index 20e5d0ce54..f8681f74a6 100644 --- a/tests/unit/test_z_cmdline.py +++ b/tests/unit/test_z_cmdline.py @@ -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",