Skip to content

Commit

Permalink
Add Provision Arguments to ToxParser (#3246)
Browse files Browse the repository at this point in the history
  • Loading branch information
seyidaniels committed Mar 21, 2024
1 parent 380d2e2 commit 2f6667f
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 75 deletions.
1 change: 1 addition & 0 deletions docs/changelog/3190.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add provision arguments to ToxParser to fix crash when provisioning new tox environment without list-dependencies by :user:`seyidaniels`
71 changes: 70 additions & 1 deletion src/tox/config/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import argparse
import logging
import os
import random
import sys
from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentError, ArgumentParser, Namespace
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, cast

from tox.config.loader.str_convert import StrConvert
from tox.plugin import NAME
from tox.util.ci import is_ci

from .env_var import get_env_var
from .ini import IniConfig
Expand Down Expand Up @@ -178,8 +180,75 @@ def add_command(
excl_group = group.add_mutually_exclusive_group(**e_kwargs)
for a_args, _, a_kwargs in arguments:
excl_group.add_argument(*a_args, **a_kwargs)
self._add_provision_arguments(sub_parser)
return sub_parser

def _add_provision_arguments(self, sub_parser: ToxParser) -> None: # noqa: PLR6301
sub_parser.add_argument(
"--result-json",
dest="result_json",
metavar="path",
of_type=Path,
default=None,
help="write a JSON file with detailed information about all commands and results involved",
)

class SeedAction(Action):
def __call__(
self,
parser: ArgumentParser, # noqa: ARG002
namespace: Namespace,
values: str | Sequence[Any] | None,
option_string: str | None = None, # noqa: ARG002
) -> None:
if values == "notset":
result = None
else:
try:
result = int(cast(str, values))
if result <= 0:
msg = "must be greater than zero"
raise ValueError(msg) # noqa: TRY301
except ValueError as exc:
raise ArgumentError(self, str(exc)) from exc
setattr(namespace, self.dest, result)

if os.environ.get("PYTHONHASHSEED", "random") != "random":
hashseed_default = int(os.environ["PYTHONHASHSEED"])
else:
hashseed_default = random.randint(1, 1024 if sys.platform == "win32" else 4294967295) # noqa: S311
sub_parser.add_argument(
"--hashseed",
metavar="SEED",
help="set PYTHONHASHSEED to SEED before running commands. Defaults to a random integer in the range "
"[1, 4294967295] ([1, 1024] on Windows). Passing 'notset' suppresses this behavior.",
action=SeedAction,
of_type=Optional[int], # type: ignore[arg-type]
default=hashseed_default,
dest="hash_seed",
)
sub_parser.add_argument(
"--discover",
dest="discover",
nargs="+",
metavar="path",
help="for Python discovery first try the Python executables under these paths",
default=[],
)
list_deps = sub_parser.add_mutually_exclusive_group()
list_deps.add_argument(
"--list-dependencies",
action="store_true",
default=is_ci(),
help="list the dependencies installed during environment setup",
)
list_deps.add_argument(
"--no-list-dependencies",
action="store_false",
dest="list_dependencies",
help="never list the dependencies installed during environment setup",
)

def add_argument_group(self, *args: Any, **kwargs: Any) -> Any:
result = super().add_argument_group(*args, **kwargs)
if self.of_cmd is None and args not in {("positional arguments",), ("optional arguments",)}:
Expand Down
2 changes: 1 addition & 1 deletion src/tox/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
tox uses `pluggy <https://pluggy.readthedocs.io/en/stable/>`_ to customize the default behavior. It provides an
extension mechanism for plugin management an calling hooks.
extension mechanism for plugin management by calling hooks.
Pluggy discovers a plugin by looking up for entry-points named ``tox``, for example in a pyproject.toml:
Expand Down
72 changes: 1 addition & 71 deletions src/tox/session/cmd/run/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

import logging
import os
import random
import sys
import time
from argparse import Action, ArgumentError, ArgumentParser, Namespace
from concurrent.futures import CancelledError, Future, ThreadPoolExecutor, as_completed
Expand All @@ -21,7 +19,6 @@
from tox.journal import write_journal
from tox.session.cmd.run.single import ToxEnvRunResult, run_one
from tox.tox_env.runner import RunToxEnv
from tox.util.ci import is_ci
from tox.util.graph import stable_topological_sort
from tox.util.spinner import MISS_DURATION, Spinner

Expand Down Expand Up @@ -62,17 +59,8 @@ def __call__(
setattr(namespace, self.dest, path)


def env_run_create_flags(parser: ArgumentParser, mode: str) -> None: # noqa: C901
def env_run_create_flags(parser: ArgumentParser, mode: str) -> None:
# mode can be one of: run, run-parallel, legacy, devenv, config
if mode not in {"config", "depends"}:
parser.add_argument(
"--result-json",
dest="result_json",
metavar="path",
of_type=Path,
default=None,
help="write a JSON file with detailed information about all commands and results involved",
)
if mode not in {"devenv", "depends"}:
parser.add_argument(
"-s",
Expand Down Expand Up @@ -114,71 +102,13 @@ def env_run_create_flags(parser: ArgumentParser, mode: str) -> None: # noqa: C9
help="install package in development mode",
dest="develop",
)
if mode != "depends":

class SeedAction(Action):
def __call__(
self,
parser: ArgumentParser, # noqa: ARG002
namespace: Namespace,
values: str | Sequence[Any] | None,
option_string: str | None = None, # noqa: ARG002
) -> None:
if values == "notset":
result = None
else:
try:
result = int(cast(str, values))
if result <= 0:
msg = "must be greater than zero"
raise ValueError(msg) # noqa: TRY301
except ValueError as exc:
raise ArgumentError(self, str(exc)) from exc
setattr(namespace, self.dest, result)

if os.environ.get("PYTHONHASHSEED", "random") != "random":
hashseed_default = int(os.environ["PYTHONHASHSEED"])
else:
hashseed_default = random.randint(1, 1024 if sys.platform == "win32" else 4294967295) # noqa: S311

parser.add_argument(
"--hashseed",
metavar="SEED",
help="set PYTHONHASHSEED to SEED before running commands. Defaults to a random integer in the range "
"[1, 4294967295] ([1, 1024] on Windows). Passing 'notset' suppresses this behavior.",
action=SeedAction,
of_type=Optional[int],
default=hashseed_default,
dest="hash_seed",
)
parser.add_argument(
"--discover",
dest="discover",
nargs="+",
metavar="path",
help="for Python discovery first try the Python executables under these paths",
default=[],
)
if mode != "depends":
parser.add_argument(
"--no-recreate-pkg",
dest="no_recreate_pkg",
help="if recreate is set do not recreate packaging tox environment(s)",
action="store_true",
)
list_deps = parser.add_mutually_exclusive_group()
list_deps.add_argument(
"--list-dependencies",
action="store_true",
default=is_ci(),
help="list the dependencies installed during environment setup",
)
list_deps.add_argument(
"--no-list-dependencies",
action="store_false",
dest="list_dependencies",
help="never list the dependencies installed during environment setup",
)
if mode not in {"devenv", "config", "depends"}:
parser.add_argument(
"--skip-pkg-install",
Expand Down
16 changes: 16 additions & 0 deletions tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,19 @@ def test_provision_conf_file(tox_project: ToxProjectCreator, tmp_path: Path, rel
conf_path = str(Path(project.path.name) / "tox.ini") if relative_path else str(project.path / "tox.ini")
result = project.run("c", "--conf", conf_path, "-e", "py", from_cwd=tmp_path)
result.assert_success()


@pytest.mark.parametrize("subcommand", ["r", "p", "de", "l", "d", "c", "q", "e", "le"])
def test_provision_default_arguments_exists(tox_project: ToxProjectCreator, subcommand: str) -> None:
ini = r"""
[tox]
requires =
tox<4.14
[testenv]
package = skip
"""
project = tox_project({"tox.ini": ini})
project.patch_execute(lambda r: 0 if "install" in r.run_id else None)
outcome = project.run(subcommand)
for argument in ["result_json", "hash_seed", "discover", "list_dependencies"]:
assert hasattr(outcome.state.conf.options, argument)
4 changes: 2 additions & 2 deletions tests/tox_env/python/test_python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def test_python_hash_seed_from_env_and_disable(tox_project: ToxProjectCreator) -

@pytest.mark.parametrize("in_ci", [True, False])
def test_list_installed_deps(in_ci: bool, tox_project: ToxProjectCreator, mocker: MockerFixture) -> None:
mocker.patch("tox.session.cmd.run.common.is_ci", return_value=in_ci)
mocker.patch("tox.config.cli.parser.is_ci", return_value=in_ci)
result = tox_project({"tox.ini": "[testenv]\nskip_install = true"}).run("r", "-e", "py")
if in_ci:
assert "pip==" in result.out
Expand All @@ -271,7 +271,7 @@ def test_list_installed_deps_explicit_cli(
tox_project: ToxProjectCreator,
mocker: MockerFixture,
) -> None:
mocker.patch("tox.session.cmd.run.common.is_ci", return_value=in_ci)
mocker.patch("tox.config.cli.parser.is_ci", return_value=in_ci)
result = tox_project({"tox.ini": "[testenv]\nskip_install = true"}).run(list_deps, "r", "-e", "py")
if list_deps == "--list-dependencies":
assert "pip==" in result.out
Expand Down

0 comments on commit 2f6667f

Please sign in to comment.