diff --git a/provision-backports.patch b/provision-backports.patch new file mode 100644 index 0000000..36f358d --- /dev/null +++ b/provision-backports.patch @@ -0,0 +1,235 @@ +diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py +index c21222c..93805ba 100644 +--- a/src/tox/config/__init__.py ++++ b/src/tox/config/__init__.py +@@ -2,6 +2,7 @@ from __future__ import print_function + + import argparse + import itertools ++import json + import os + import random + import re +@@ -538,6 +539,16 @@ def tox_addoption(parser): + action="store_true", + help="override alwayscopy setting to True in all envs", + ) ++ parser.add_argument( ++ "--no-provision", ++ action="store", ++ nargs="?", ++ default=False, ++ const=True, ++ metavar="REQUIRES_JSON", ++ help="do not perform provision, but fail and if a path was provided " ++ "write provision metadata as JSON to it", ++ ) + + cli_skip_missing_interpreter(parser) + parser.add_argument("--workdir", metavar="PATH", help="tox working directory") +@@ -1234,11 +1245,11 @@ class ParseIni(object): + feedback("--devenv requires only a single -e", sysexit=True) + + def handle_provision(self, config, reader): +- requires_list = reader.getlist("requires") ++ config.requires = reader.getlist("requires") + config.minversion = reader.getstring("minversion", None) + config.provision_tox_env = name = reader.getstring("provision_tox_env", ".tox") + min_version = "tox >= {}".format(config.minversion or tox.__version__) +- deps = self.ensure_requires_satisfied(config, requires_list, min_version) ++ deps = self.ensure_requires_satisfied(config, config.requires, min_version) + if config.run_provision: + section_name = "testenv:{}".format(name) + if section_name not in self._cfg.sections: +@@ -1254,8 +1265,8 @@ class ParseIni(object): + # raise on unknown args + self.config._parser.parse_cli(args=self.config.args, strict=True) + +- @staticmethod +- def ensure_requires_satisfied(config, requires, min_version): ++ @classmethod ++ def ensure_requires_satisfied(cls, config, requires, min_version): + missing_requirements = [] + failed_to_parse = False + deps = [] +@@ -1282,12 +1293,33 @@ class ParseIni(object): + missing_requirements.append(str(requirements.Requirement(require))) + if failed_to_parse: + raise tox.exception.BadRequirement() ++ if config.option.no_provision and missing_requirements: ++ msg = "provisioning explicitly disabled within {}, but missing {}" ++ if config.option.no_provision is not True: # it's a path ++ msg += " and wrote to {}" ++ cls.write_requires_to_json_file(config) ++ raise tox.exception.Error( ++ msg.format(sys.executable, missing_requirements, config.option.no_provision) ++ ) + if WITHIN_PROVISION and missing_requirements: + msg = "break infinite loop provisioning within {} missing {}" + raise tox.exception.Error(msg.format(sys.executable, missing_requirements)) + config.run_provision = bool(len(missing_requirements)) + return deps + ++ @staticmethod ++ def write_requires_to_json_file(config): ++ requires_dict = { ++ "minversion": config.minversion, ++ "requires": config.requires, ++ } ++ try: ++ with open(config.option.no_provision, "w", encoding="utf-8") as outfile: ++ json.dump(requires_dict, outfile, indent=4) ++ except TypeError: # Python 2 ++ with open(config.option.no_provision, "w") as outfile: ++ json.dump(requires_dict, outfile, indent=4, encoding="utf-8") ++ + def parse_build_isolation(self, config, reader): + config.isolated_build = reader.getbool("isolated_build", False) + config.isolated_build_env = reader.getstring("isolated_build_env", ".package") +diff --git a/tests/unit/session/test_provision.py b/tests/unit/session/test_provision.py +index aa631c0..710df60 100644 +--- a/tests/unit/session/test_provision.py ++++ b/tests/unit/session/test_provision.py +@@ -1,5 +1,6 @@ + from __future__ import absolute_import, unicode_literals + ++import json + import os + import shutil + import subprocess +@@ -42,6 +43,35 @@ def test_provision_min_version_is_requires(newconfig, next_tox_major): + assert config.ignore_basepython_conflict is False + + ++def test_provision_config_has_minversion_and_requires(newconfig, next_tox_major): ++ with pytest.raises(MissingRequirement) as context: ++ newconfig( ++ [], ++ """\ ++ [tox] ++ minversion = {} ++ requires = ++ setuptools > 2 ++ pip > 3 ++ """.format( ++ next_tox_major, ++ ), ++ ) ++ config = context.value.config ++ ++ assert config.run_provision is True ++ assert config.minversion == next_tox_major ++ assert config.requires == ["setuptools > 2", "pip > 3"] ++ ++ ++def test_provision_config_empty_minversion_and_requires(newconfig, next_tox_major): ++ config = newconfig([], "") ++ ++ assert config.run_provision is False ++ assert config.minversion is None ++ assert config.requires == [] ++ ++ + def test_provision_tox_change_name(newconfig): + config = newconfig( + [], +@@ -149,6 +179,99 @@ def test_provision_cli_args_not_ignored_if_provision_false(cmd, initproj): + result.assert_fail(is_run_test_env=False) + + ++parametrize_json_path = pytest.mark.parametrize("json_path", [None, "missing.json"]) ++ ++ ++@parametrize_json_path ++def test_provision_does_not_fail_with_no_provision_no_reason(cmd, initproj, json_path): ++ p = initproj("test-0.1", {"tox.ini": "[tox]"}) ++ result = cmd("--no-provision", *([json_path] if json_path else [])) ++ result.assert_success(is_run_test_env=True) ++ assert not (p / "missing.json").exists() ++ ++ ++@parametrize_json_path ++def test_provision_fails_with_no_provision_next_tox(cmd, initproj, next_tox_major, json_path): ++ p = initproj( ++ "test-0.1", ++ { ++ "tox.ini": """\ ++ [tox] ++ minversion = {} ++ """.format( ++ next_tox_major, ++ ) ++ }, ++ ) ++ result = cmd("--no-provision", *([json_path] if json_path else [])) ++ result.assert_fail(is_run_test_env=False) ++ if json_path: ++ missing = json.loads((p / json_path).read_text("utf-8")) ++ assert missing["minversion"] == next_tox_major ++ ++ ++@parametrize_json_path ++def test_provision_fails_with_no_provision_missing_requires(cmd, initproj, json_path): ++ p = initproj( ++ "test-0.1", ++ { ++ "tox.ini": """\ ++ [tox] ++ requires = ++ virtualenv > 99999999 ++ """ ++ }, ++ ) ++ result = cmd("--no-provision", *([json_path] if json_path else [])) ++ result.assert_fail(is_run_test_env=False) ++ if json_path: ++ missing = json.loads((p / json_path).read_text("utf-8")) ++ assert missing["requires"] == ["virtualenv > 99999999"] ++ ++ ++@parametrize_json_path ++def test_provision_does_not_fail_with_satisfied_requires(cmd, initproj, next_tox_major, json_path): ++ p = initproj( ++ "test-0.1", ++ { ++ "tox.ini": """\ ++ [tox] ++ minversion = 0 ++ requires = ++ setuptools > 2 ++ pip > 3 ++ """ ++ }, ++ ) ++ result = cmd("--no-provision", *([json_path] if json_path else [])) ++ result.assert_success(is_run_test_env=True) ++ assert not (p / "missing.json").exists() ++ ++ ++@parametrize_json_path ++def test_provision_fails_with_no_provision_combined(cmd, initproj, next_tox_major, json_path): ++ p = initproj( ++ "test-0.1", ++ { ++ "tox.ini": """\ ++ [tox] ++ minversion = {} ++ requires = ++ setuptools > 2 ++ pip > 3 ++ """.format( ++ next_tox_major, ++ ) ++ }, ++ ) ++ result = cmd("--no-provision", *([json_path] if json_path else [])) ++ result.assert_fail(is_run_test_env=False) ++ if json_path: ++ missing = json.loads((p / json_path).read_text("utf-8")) ++ assert missing["minversion"] == next_tox_major ++ assert missing["requires"] == ["setuptools > 2", "pip > 3"] ++ ++ + @pytest.fixture(scope="session") + def wheel(tmp_path_factory): + """create a wheel for a project""" diff --git a/python-tox.spec b/python-tox.spec index 58a1f6a..fe9bdf0 100644 --- a/python-tox.spec +++ b/python-tox.spec @@ -8,13 +8,19 @@ %global pypi_name tox Name: python-%{pypi_name} Version: 3.15.2 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Virtualenv-based automation of test activities License: MIT URL: https://tox.readthedocs.io/ Source0: %{pypi_source} +# Expose tox requires via the config object +# https://github.com/tox-dev/tox/pull/1919 +# Add --no-provision flag +# https://github.com/tox-dev/tox/pull/1922 +Patch1: provision-backports.patch + BuildArch: noarch %description @@ -68,7 +74,6 @@ BuildRequires: python3-filelock BuildRequires: python3-flaky BuildRequires: python3-freezegun BuildRequires: python3-packaging -BuildRequires: python3-pathlib2 BuildRequires: python3-pip BuildRequires: python3-pluggy >= 0.12 BuildRequires: python3-poetry @@ -104,6 +109,10 @@ can use for: %prep %autosetup -p1 -n %{pypi_name}-%{version} +# Pathlib2 was retired in Fedora, instead of unretiring it, +# it's enough to use pathlib instead. +find . -type f -name "*.py" -print0 | xargs -0 sed -i "s/pathlib2/pathlib/g" + # remove bundled egg-info rm -rf %{pypi_name}.egg-info @@ -146,6 +155,10 @@ rm -rf .path %{python3_sitelib}/%{pypi_name}-%{version}-py%{python3_version}.egg-info/ %changelog +* Thu Mar 18 2021 Miro Hrončok - 3.15.2-2 +- Expose tox requires via the config object +- Add --no-provision flag + * Mon Jun 08 2020 Miro Hrončok - 3.15.2-1 - Update to 3.15.2 (#1844689)