| |
@@ -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"""
|
| |
Related to https://bugzilla.redhat.com/show_bug.cgi?id=1922495