diff options
author | Bernát Gábor <gaborjbernat@gmail.com> | 2022-12-16 10:22:30 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-16 10:22:30 -0800 |
commit | 5cef03092cc06fd598d66a821751cdb1c4789f1c (patch) | |
tree | b86b8928dd05080ae9f6ebafbbfa6077a00a4031 | |
parent | d074f3fa05cd10acb789ad23e62310c4a179e013 (diff) | |
download | tox-git-5cef03092cc06fd598d66a821751cdb1c4789f1c.tar.gz |
Fix python hash seed not being set (#2739)
Resolves https://github.com/tox-dev/tox/issues/2645
-rw-r--r-- | docs/changelog/2645.bugfix.rst | 1 | ||||
-rw-r--r-- | src/tox/session/cmd/run/common.py | 29 | ||||
-rw-r--r-- | src/tox/tox_env/python/api.py | 7 | ||||
-rw-r--r-- | tests/config/cli/test_cli_env_var.py | 5 | ||||
-rw-r--r-- | tests/config/cli/test_cli_ini.py | 6 | ||||
-rw-r--r-- | tests/config/test_set_env.py | 14 | ||||
-rw-r--r-- | tests/tox_env/python/test_python_api.py | 48 | ||||
-rw-r--r-- | tox.ini | 5 |
8 files changed, 100 insertions, 15 deletions
diff --git a/docs/changelog/2645.bugfix.rst b/docs/changelog/2645.bugfix.rst new file mode 100644 index 00000000..a7b7e806 --- /dev/null +++ b/docs/changelog/2645.bugfix.rst @@ -0,0 +1 @@ +Fix python hash seed not being set - by :user:`gaborbernat`. diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index 794e249a..e8a5eeb7 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -3,6 +3,8 @@ from __future__ import annotations 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 @@ -108,14 +110,35 @@ def env_run_create_flags(parser: ArgumentParser, mode: str) -> None: help="install package in development mode", dest="develop", ) - if mode not in ("config", "depends"): + if mode not in ("depends",): + + class SeedAction(Action): + def __call__( + self, + parser: ArgumentParser, # noqa: U100 + namespace: Namespace, + values: str | Sequence[Any] | None, + option_string: str | None = None, # noqa: U100 + ) -> None: + if values == "notset": + result = None + else: + try: + result = int(cast(str, values)) + if result <= 0: + raise ValueError("must be greater than zero") + except ValueError as exc: + raise ArgumentError(self, str(exc)) + setattr(namespace, self.dest, result) + 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 'noset' suppresses this behavior.", - type=str, - default="noset", + action=SeedAction, + of_type=Optional[int], + default=random.randint(1, 1024 if sys.platform == "win32" else 4294967295), dest="hash_seed", ) parser.add_argument( diff --git a/src/tox/tox_env/python/api.py b/src/tox/tox_env/python/api.py index 6f25dd6a..bc841ad8 100644 --- a/src/tox/tox_env/python/api.py +++ b/src/tox/tox_env/python/api.py @@ -88,6 +88,13 @@ class Python(ToxEnv, ABC): self.conf.add_constant("py_dot_ver", "<python major>.<python minor>", value=self.py_dot_ver) self.conf.add_constant("py_impl", "python implementation", value=self.py_impl) + def _default_set_env(self) -> dict[str, str]: + env = super()._default_set_env() + hash_seed: int | None = getattr(self.options, "hash_seed", None) + if hash_seed is not None: + env["PYTHONHASHSEED"] = str(hash_seed) + return env + def py_dot_ver(self) -> str: return self.base_python.version_dot diff --git a/tests/config/cli/test_cli_env_var.py b/tests/config/cli/test_cli_env_var.py index a938b641..61dbf962 100644 --- a/tests/config/cli/test_cli_env_var.py +++ b/tests/config/cli/test_cli_env_var.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Callable +from unittest.mock import ANY import pytest @@ -53,7 +54,7 @@ def test_verbose_no_test() -> None: "package_only": False, "install_pkg": None, "develop": False, - "hash_seed": "noset", + "hash_seed": ANY, "discover": [], "parallel": 0, "parallel_live": False, @@ -91,7 +92,7 @@ def test_env_var_exhaustive_parallel_values( "discover": [], "env": CliEnv(["py37", "py36"]), "force_dep": [], - "hash_seed": "noset", + "hash_seed": ANY, "install_pkg": None, "no_provision": False, "list_envs": False, diff --git a/tests/config/cli/test_cli_ini.py b/tests/config/cli/test_cli_ini.py index 6d7e245b..1022312a 100644 --- a/tests/config/cli/test_cli_ini.py +++ b/tests/config/cli/test_cli_ini.py @@ -5,6 +5,7 @@ import sys import textwrap from pathlib import Path from typing import Any, Callable +from unittest.mock import ANY import pytest from pytest_mock import MockerFixture @@ -67,6 +68,7 @@ def test_ini_empty( to.unlink() missing_options = get_options("r") + missing_options.parsed.hash_seed = ANY assert vars(missing_options.parsed) == vars(options.parsed) @@ -79,7 +81,7 @@ def default_options(tmp_path: Path) -> dict[str, Any]: "develop": False, "discover": [], "env": CliEnv(), - "hash_seed": "noset", + "hash_seed": ANY, "install_pkg": None, "no_test": False, "override": [], @@ -112,7 +114,7 @@ def test_ini_exhaustive_parallel_values(exhaustive_ini: Path, core_handlers: dic "develop": False, "discover": [], "env": CliEnv(["py37", "py36"]), - "hash_seed": "noset", + "hash_seed": ANY, "install_pkg": None, "no_test": True, "override": [Override("a=b"), Override("c=d")], diff --git a/tests/config/test_set_env.py b/tests/config/test_set_env.py index fd4b07f4..453d73ae 100644 --- a/tests/config/test_set_env.py +++ b/tests/config/test_set_env.py @@ -3,6 +3,7 @@ from __future__ import annotations import sys from pathlib import Path from typing import Any +from unittest.mock import ANY import pytest from pytest_mock import MockerFixture @@ -60,9 +61,9 @@ def eval_set_env(tox_project: ToxProjectCreator) -> EvalSetEnv: def test_set_env_default(eval_set_env: EvalSetEnv) -> None: set_env = eval_set_env("") keys = list(set_env) - assert keys == ["PIP_DISABLE_PIP_VERSION_CHECK", "PYTHONIOENCODING"] + assert keys == ["PYTHONHASHSEED", "PIP_DISABLE_PIP_VERSION_CHECK", "PYTHONIOENCODING"] values = [set_env.load(k) for k in keys] - assert values == ["1", "utf-8"] + assert values == [ANY, "1", "utf-8"] def test_set_env_self_key(eval_set_env: EvalSetEnv, monkeypatch: MonkeyPatch) -> None: @@ -120,7 +121,13 @@ def test_set_env_replacer(eval_set_env: EvalSetEnv, monkeypatch: MonkeyPatch) -> monkeypatch.setenv("MAGIC", "\nb=2\n") set_env = eval_set_env("[testenv]\npackage=skip\nset_env=a=1\n {env:MAGIC}") env = {k: set_env.load(k) for k in set_env} - assert env == {"PIP_DISABLE_PIP_VERSION_CHECK": "1", "a": "1", "b": "2", "PYTHONIOENCODING": "utf-8"} + assert env == { + "PIP_DISABLE_PIP_VERSION_CHECK": "1", + "a": "1", + "b": "2", + "PYTHONIOENCODING": "utf-8", + "PYTHONHASHSEED": ANY, + } def test_set_env_honor_override(eval_set_env: EvalSetEnv) -> None: @@ -143,6 +150,7 @@ def test_set_env_environment_file(eval_set_env: EvalSetEnv) -> None: content = {k: set_env.load(k) for k in set_env} assert content == { "PIP_DISABLE_PIP_VERSION_CHECK": "1", + "PYTHONHASHSEED": ANY, "A": "1", "B": "2", "C": "1", diff --git a/tests/tox_env/python/test_python_api.py b/tests/tox_env/python/test_python_api.py index aa4c1470..6d8f2c80 100644 --- a/tests/tox_env/python/test_python_api.py +++ b/tests/tox_env/python/test_python_api.py @@ -131,3 +131,51 @@ def test_base_python_env_conflict_show_conf(tox_project: ToxProjectCreator, igno f" base python py{py_ver_next}'{',' if comma_in_exc else ''})\n" ) result.assert_out_err(out, "") + + +def test_python_set_hash_seed(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip\ncommands=python -c 'import os; print(os.environ[\"PYTHONHASHSEED\"])'" + prj = tox_project({"tox.ini": ini}) + result = prj.run("r", "-e", "py", "--hashseed", "10") + result.assert_success() + assert result.out.splitlines()[1] == "10" + + +def test_python_generate_hash_seed(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip\ncommands=python -c 'import os; print(os.environ[\"PYTHONHASHSEED\"])'" + prj = tox_project({"tox.ini": ini}) + result = prj.run("r", "-e", "py") + result.assert_success() + assert 1 <= int(result.out.splitlines()[1]) <= (1024 if sys.platform == "win32" else 4294967295) + + +def test_python_keep_hash_seed(tox_project: ToxProjectCreator) -> None: + ini = """ + [testenv] + package=skip + set_env=PYTHONHASHSEED=12 + commands=python -c 'import os; print(os.environ["PYTHONHASHSEED"])' + """ + result = tox_project({"tox.ini": ini}).run("r", "-e", "py") + result.assert_success() + assert result.out.splitlines()[1] == "12" + + +def test_python_disable_hash_seed(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip\ncommands=python -c 'import os; print(os.environ.get(\"PYTHONHASHSEED\"))'" + prj = tox_project({"tox.ini": ini}) + result = prj.run("r", "-e", "py", "--hashseed", "notset") + result.assert_success() + assert result.out.splitlines()[1] == "None" + + +def test_python_set_hash_seed_negative(tox_project: ToxProjectCreator) -> None: + result = tox_project({"tox.ini": ""}).run("r", "-e", "py", "--hashseed", "-1") + result.assert_failed(2) + assert "tox run: error: argument --hashseed: must be greater than zero" in result.err + + +def test_python_set_hash_seed_incorrect(tox_project: ToxProjectCreator) -> None: + result = tox_project({"tox.ini": ""}).run("r", "-e", "py", "--hashseed", "ok") + result.assert_failed(2) + assert "tox run: error: argument --hashseed: invalid literal for int() with base 10: 'ok'" in result.err @@ -48,11 +48,6 @@ commands = pre-commit run --all-files --show-diff-on-failure {tty:--color=always} {posargs} python -c 'print(r"hint: run {envbindir}{/}pre-commit install to add checks as pre-commit hook")' -[testenv:py311] -setenv = - {[testenv]setenv} - AIOHTTP_NO_EXTENSIONS = 1 - [testenv:type] description = run type check on code base setenv = |