Skip to content

Commit

Permalink
Add --no-provision flag (#1922)
Browse files Browse the repository at this point in the history
tox can now be invoked with a new --no-provision flag that prevents provision,
if requires or minversion are not satisfied, tox will fail;
if a path is specified as an argument to the flag
(e.g. as `tox --no-provision missing.json`) and provision is prevented,
provision metadata are written as JSON to that path.

Fixes #1921
  • Loading branch information
hroncok committed Mar 3, 2021
1 parent a586b2a commit 93a9667
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/1921.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
tox can now be invoked with a new ``--no-provision`` flag that prevents provision,
if :conf:`requires` or :conf:`minversion` are not satisfied,
tox will fail;
if a path is specified as an argument to the flag
(e.g. as ``tox --no-provision missing.json``) and provision is prevented,
provision metadata are written as JSON to that path - by :user:`hroncok`
15 changes: 15 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ Global settings are defined under the ``tox`` section as:
than this the tool will create an environment and provision it with a version of
tox that satisfies this under :conf:`provision_tox_env`.

.. versionchanged:: 3.23.0

When tox is invoked with the ``--no-provision`` flag,
the provision won't be attempted, tox will fail instead.

.. conf:: requires ^ LIST of PEP-508

.. versionadded:: 3.2.0
Expand All @@ -54,13 +59,23 @@ Global settings are defined under the ``tox`` section as:
requires = tox-pipenv
setuptools >= 30.0.0
.. versionchanged:: 3.23.0

When tox is invoked with the ``--no-provision`` flag,
the provision won't be attempted, tox will fail instead.

.. conf:: provision_tox_env ^ string ^ .tox

.. versionadded:: 3.8.0

Name of the virtual environment used to provision a tox having all dependencies specified
inside :conf:`requires` and :conf:`minversion`.

.. versionchanged:: 3.23.0

When tox is invoked with the ``--no-provision`` flag,
the provision won't be attempted, tox will fail instead.

.. conf:: toxworkdir ^ PATH ^ {toxinidir}/.tox

Directory for tox to generate its environments into, will be created if it does not exist.
Expand Down
36 changes: 34 additions & 2 deletions src/tox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import argparse
import itertools
import json
import os
import random
import re
Expand Down Expand Up @@ -572,6 +573,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")
Expand Down Expand Up @@ -1318,8 +1329,8 @@ def handle_provision(self, config, reader):
# 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 = []
Expand All @@ -1346,12 +1357,33 @@ def ensure_requires_satisfied(config, requires, min_version):
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")
Expand Down
94 changes: 94 additions & 0 deletions tests/unit/session/test_provision.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals

import json
import os
import shutil
import subprocess
Expand Down Expand Up @@ -185,6 +186,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"""
Expand Down

0 comments on commit 93a9667

Please sign in to comment.