Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --no-provision flag #1922

Merged
merged 1 commit into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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