diff --git a/doc/config.rst b/doc/config.rst index ef9ac5ccb9..db3ca7998a 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -120,8 +120,12 @@ Global settings are defined under the ``tox`` section as: .. code-block:: ini [tox] - requires = setuptools >= 30.0.0 - py + requires = tox-venv + setuptools >= 30.0.0 + + .. note:: tox does **not** install those required packages for you. tox only checks if the + requirements are satisfied and crashes early with an helpful error rather then later + in the process. .. conf:: isolated_build ^ true|false ^ false diff --git a/src/tox/config.py b/src/tox/config.py index e560c22147..2bcd0f7be8 100755 --- a/src/tox/config.py +++ b/src/tox/config.py @@ -1084,19 +1084,20 @@ def _make_thread_safe_path(self, config, attr, unique_id): @staticmethod def ensure_requires_satisfied(specified): - fail = False + missing_requirements = [] for s in specified: try: pkg_resources.get_distribution(s) except pkg_resources.RequirementParseError: raise except Exception: - fail = True - print( - "requirement missing {}".format(pkg_resources.Requirement(s)), file=sys.stderr + missing_requirements.append(str(pkg_resources.Requirement(s))) + if missing_requirements: + raise tox.exception.MissingRequirement( + "Packages {} need to be installed alongside tox in {}".format( + ", ".join(missing_requirements), sys.executable ) - if fail: - raise RuntimeError("not all requirements satisfied, install them alongside tox") + ) def _list_section_factors(self, section): factors = set() diff --git a/src/tox/exception.py b/src/tox/exception.py index 246fca7fb0..4ad07e460a 100644 --- a/src/tox/exception.py +++ b/src/tox/exception.py @@ -80,6 +80,10 @@ class MissingDependency(Error): """A dependency could not be found or determined.""" +class MissingRequirement(Error): + """A requirement defined in :config:`require` is not met.""" + + class MinVersionError(Error): """The installed tox version is lower than requested minversion.""" diff --git a/src/tox/interpreters.py b/src/tox/interpreters.py index ccdbfdb53c..e3fd9902e6 100644 --- a/src/tox/interpreters.py +++ b/src/tox/interpreters.py @@ -151,6 +151,7 @@ def tox_get_python_executable(envconfig): # The standard names are in predictable places. actual = r"c:\python{}\python.exe".format("".join(groups)) else: + actual = win32map.get(name, None) if actual: actual = py.path.local(actual) diff --git a/src/tox/session.py b/src/tox/session.py index 4d5c782336..e2c89f675e 100644 --- a/src/tox/session.py +++ b/src/tox/session.py @@ -51,7 +51,7 @@ def main(args): raise SystemExit(retcode) except KeyboardInterrupt: raise SystemExit(2) - except tox.exception.MinVersionError as e: + except (tox.exception.MinVersionError, tox.exception.MissingRequirement) as e: r = Reporter(None) r.error(str(e)) raise SystemExit(1) diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 1981c2b80d..9fbeefb013 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -2644,26 +2644,20 @@ def test_commands_with_backslash(self, newconfig): assert envconfig.commands[0] == ["some", r"hello\world"] -def test_plugin_require(newconfig, capsys): +def test_plugin_require(newconfig): inisource = """ [tox] requires = tox name[foo,bar]>=2,<3; python_version>"2.0" and os_name=='a' b """ - with pytest.raises( - RuntimeError, match="not all requirements satisfied, install them alongside tox" - ): + with pytest.raises(tox.exception.MissingRequirement) as exc_info: newconfig([], inisource) - out, err = capsys.readouterr() - assert err.strip() == "\n".join( - [ - 'requirement missing name[bar,foo]<3,>=2; python_version > "2.0" and os_name == "a"', - "requirement missing b", - ] + assert exc_info.value.args[0] == ( + r'Packages name[bar,foo]<3,>=2; python_version > "2.0" and os_name == "a", b ' + r"need to be installed alongside tox in {}".format(sys.executable) ) - assert not out def test_isolated_build_env_cannot_be_in_envlist(newconfig, capsys): diff --git a/tests/unit/test_docs.py b/tests/unit/test_docs.py index c3e9b7b70e..4d19e8b3d3 100644 --- a/tests/unit/test_docs.py +++ b/tests/unit/test_docs.py @@ -4,6 +4,7 @@ import pytest +import tox from tox.config import parseconfig INI_BLOCK_RE = re.compile( @@ -20,9 +21,9 @@ RST_FILES = [] TOX_ROOT = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) for root, _, filenames in os.walk(os.path.join(TOX_ROOT, "doc")): - for f in filenames: - if f.endswith(".rst"): - RST_FILES.append(os.path.join(root, f)) + for filename in filenames: + if filename.endswith(".rst"): + RST_FILES.append(os.path.join(root, filename)) def test_some_files_exist(): @@ -35,10 +36,12 @@ def test_all_rst_ini_blocks_parse(filename, tmpdir): contents = f.read() for match in INI_BLOCK_RE.finditer(contents): code = textwrap.dedent(match.group("code")) - f = tmpdir.join("tox.ini") - f.write(code) + config_path = tmpdir / "tox.ini" + config_path.write(code) try: - parseconfig(("-c", str(f))) + parseconfig(["-c", str(config_path)]) + except tox.exception.MissingRequirement: + assert "requires = tox-venv" in str(code) except Exception as e: raise AssertionError( "Error parsing ini block\n\n"