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 Provision Arguments to ToxParser #3246

Merged
merged 4 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
7 changes: 7 additions & 0 deletions tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,10 @@ 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()


def test_provision_default_arguments_exists(tox_project: ToxProjectCreator) -> None:
ini = "[tox]\nrequires = tox<4"
gaborbernat marked this conversation as resolved.
Show resolved Hide resolved
outcome = tox_project({"tox.ini": ini}).run()
seyidaniels marked this conversation as resolved.
Show resolved Hide resolved
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