diff --git a/docs/changelog/2942.feature.rst b/docs/changelog/2942.feature.rst new file mode 100644 index 000000000..271ee78ca --- /dev/null +++ b/docs/changelog/2942.feature.rst @@ -0,0 +1,5 @@ +Make --hashseed default to PYTHONHASHSEED, if defined - by :user:`paravoid`. +The main motivation for this is to able to set the hash seed when building the +documentation with "tox -e docs", and thus avoid embedding a random value in +the tox documentation for --help. This caused documentation builds to fail to +build reproducibly. diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index f61a45a4f..25894ed58 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -135,6 +135,11 @@ def __call__( 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", @@ -142,7 +147,7 @@ def __call__( "[1, 4294967295] ([1, 1024] on Windows). Passing 'noset' suppresses this behavior.", action=SeedAction, of_type=Optional[int], - default=random.randint(1, 1024 if sys.platform == "win32" else 4294967295), # noqa: S311 + default=hashseed_default, dest="hash_seed", ) parser.add_argument( diff --git a/tests/tox_env/python/test_python_api.py b/tests/tox_env/python/test_python_api.py index a3a619276..159d9d772 100644 --- a/tests/tox_env/python/test_python_api.py +++ b/tests/tox_env/python/test_python_api.py @@ -2,6 +2,7 @@ import sys from typing import TYPE_CHECKING, Callable +from unittest.mock import patch import pytest @@ -220,6 +221,38 @@ def test_python_set_hash_seed_incorrect(tox_project: ToxProjectCreator) -> None: assert "tox run: error: argument --hashseed: invalid literal for int() with base 10: 'ok'" in result.err +def test_python_use_hash_seed_from_env(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip" + with patch.dict("os.environ", {"PYTHONHASHSEED": "13"}): + result = tox_project({"tox.ini": ini}).run("c", "-e", "py", "-k", "setenv") + result.assert_success() + assert "PYTHONHASHSEED=13" in result.out + + +def test_python_hash_seed_from_env_random(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip" + with patch.dict("os.environ", {"PYTHONHASHSEED": "random"}): + result = tox_project({"tox.ini": ini}).run("c", "-e", "py", "-k", "setenv") + result.assert_success() + assert "PYTHONHASHSEED=" in result.out + + +def test_python_hash_seed_from_env_and_override(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip\ncommands=python -c 'import os; print(os.environ.get(\"PYTHONHASHSEED\"))'" + with patch.dict("os.environ", {"PYTHONHASHSEED": "14"}): + result = tox_project({"tox.ini": ini}).run("r", "-e", "py", "--hashseed", "15") + result.assert_success() + assert result.out.splitlines()[1] == "15" + + +def test_python_hash_seed_from_env_and_disable(tox_project: ToxProjectCreator) -> None: + ini = "[testenv]\npackage=skip\ncommands=python -c 'import os; print(os.environ.get(\"PYTHONHASHSEED\"))'" + with patch.dict("os.environ", {"PYTHONHASHSEED": "16"}): + result = tox_project({"tox.ini": ini}).run("r", "-e", "py", "--hashseed", "notset") + result.assert_success() + assert result.out.splitlines()[1] == "None" + + @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)