From 0b61ca4499806a41fae94394ae76af4de8295d8d Mon Sep 17 00:00:00 2001 From: Arham Begani <123586002+Arham-Begani@users.noreply.github.com> Date: Sun, 18 Oct 2026 18:21:37 +0530 Subject: [PATCH 1/2] fix: preserve existing Windows env values with differently cased file keys --- src/pytest_env/plugin.py | 5 +++-- tests/test_env.py | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/pytest_env/plugin.py b/src/pytest_env/plugin.py index fcdcaeb..0fed2b9 100644 --- a/src/pytest_env/plugin.py +++ b/src/pytest_env/plugin.py @@ -96,9 +96,10 @@ def _apply_env_files( for env_file in _load_env_files(early_config, env_files_list): for key, value in dotenv_values(env_file).items(): if value is not None: - if skip_if_set and key in preexisting: + env_key = key.upper() if os.name == "nt" else key + if skip_if_set and env_key in preexisting: if actions is not None: - actions.append(("SKIP", key, preexisting[key], str(env_file))) + actions.append(("SKIP", key, preexisting[env_key], str(env_file))) else: os.environ[key] = value if actions is not None: diff --git a/tests/test_env.py b/tests/test_env.py index 20423b6..2d1b379 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -4,12 +4,13 @@ import re from pathlib import Path from textwrap import dedent +from types import SimpleNamespace from typing import TYPE_CHECKING, Final from unittest import mock import pytest -from pytest_env.plugin import _load_toml_config # ruff:ignore[import-private-name] +from pytest_env.plugin import _apply_env_files, _load_toml_config # ruff:ignore[import-private-name] if TYPE_CHECKING: from pytest_mock import MockerFixture @@ -810,3 +811,26 @@ def test_env() -> None: new_env: Final[dict[str, str]] = {"PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1", "PYTEST_PLUGINS": "pytest_env.plugin"} with mock.patch.dict(os.environ, new_env, clear=True): pytester.runpytest_subprocess(*(["--envfile", "+cli.env"] if extend else [])).assert_outcomes(passed=1) + + +@pytest.mark.parametrize("platform", ["nt", "posix"]) +@pytest.mark.parametrize("file_key", ["PYTEST_ENV_CASE", "pytest_env_case"]) +def test_env_files_skip_if_set_respects_platform_case( + tmp_path: Path, mocker: MockerFixture, platform: str, file_key: str +) -> None: + env = {"PYTEST_ENV_CASE": "original"} + mocker.patch("pytest_env.plugin.os", SimpleNamespace(name=platform, environ=env)) + (tmp_path / ".env").write_text(f"{file_key}=from_file\n", encoding="utf-8") + config = mocker.Mock(spec=pytest.Config) + config.rootpath = tmp_path + config.known_args_namespace = SimpleNamespace(envfile=None) + actions: list[tuple[str, str, str, str]] = [] + + _apply_env_files(config, [".env"], actions, skip_if_set=True) + + if platform == "posix" and file_key == "pytest_env_case": + assert env == {"PYTEST_ENV_CASE": "original", "pytest_env_case": "from_file"} + assert actions == [("SET", file_key, "from_file", str(tmp_path / ".env"))] + else: + assert env == {"PYTEST_ENV_CASE": "original"} + assert actions == [("SKIP", file_key, "original", str(tmp_path / ".env"))] From fd03c22e1c06908f6235099e408ab9402c35d836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Sun, 18 Oct 2026 08:40:56 -0700 Subject: [PATCH 2/2] test: exercise Windows env skip through pytest --- src/pytest_env/plugin.py | 3 +-- tests/test_env.py | 57 ++++++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/pytest_env/plugin.py b/src/pytest_env/plugin.py index 0fed2b9..61f8c81 100644 --- a/src/pytest_env/plugin.py +++ b/src/pytest_env/plugin.py @@ -96,8 +96,7 @@ def _apply_env_files( for env_file in _load_env_files(early_config, env_files_list): for key, value in dotenv_values(env_file).items(): if value is not None: - env_key = key.upper() if os.name == "nt" else key - if skip_if_set and env_key in preexisting: + if skip_if_set and (env_key := key.upper() if os.name == "nt" else key) in preexisting: if actions is not None: actions.append(("SKIP", key, preexisting[env_key], str(env_file))) else: diff --git a/tests/test_env.py b/tests/test_env.py index 2d1b379..04f7733 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -4,13 +4,12 @@ import re from pathlib import Path from textwrap import dedent -from types import SimpleNamespace from typing import TYPE_CHECKING, Final from unittest import mock import pytest -from pytest_env.plugin import _apply_env_files, _load_toml_config # ruff:ignore[import-private-name] +from pytest_env.plugin import _load_toml_config # ruff:ignore[import-private-name] if TYPE_CHECKING: from pytest_mock import MockerFixture @@ -813,24 +812,36 @@ def test_env() -> None: pytester.runpytest_subprocess(*(["--envfile", "+cli.env"] if extend else [])).assert_outcomes(passed=1) -@pytest.mark.parametrize("platform", ["nt", "posix"]) -@pytest.mark.parametrize("file_key", ["PYTEST_ENV_CASE", "pytest_env_case"]) -def test_env_files_skip_if_set_respects_platform_case( - tmp_path: Path, mocker: MockerFixture, platform: str, file_key: str -) -> None: - env = {"PYTEST_ENV_CASE": "original"} - mocker.patch("pytest_env.plugin.os", SimpleNamespace(name=platform, environ=env)) - (tmp_path / ".env").write_text(f"{file_key}=from_file\n", encoding="utf-8") - config = mocker.Mock(spec=pytest.Config) - config.rootpath = tmp_path - config.known_args_namespace = SimpleNamespace(envfile=None) - actions: list[tuple[str, str, str, str]] = [] - - _apply_env_files(config, [".env"], actions, skip_if_set=True) - - if platform == "posix" and file_key == "pytest_env_case": - assert env == {"PYTEST_ENV_CASE": "original", "pytest_env_case": "from_file"} - assert actions == [("SET", file_key, "from_file", str(tmp_path / ".env"))] - else: - assert env == {"PYTEST_ENV_CASE": "original"} - assert actions == [("SKIP", file_key, "original", str(tmp_path / ".env"))] +@pytest.mark.parametrize( + "file_key", + [ + pytest.param("PYTEST_ENV_CASE", id="same-case"), + pytest.param("pytest_env_case", id="different-case"), + ], +) +def test_env_files_skip_if_set_respects_platform_case(pytester: pytest.Pytester, file_key: str) -> None: + expected: Final[str] = "from_file" if os.name != "nt" and file_key == "pytest_env_case" else "original" + (pytester.path / ".env").write_text(f"{file_key}=from_file\n", encoding="utf-8") + (pytester.path / "pytest.ini").write_text( + "[pytest]\nenv_files = .env\nenv_files_skip_if_set = true\n", encoding="utf-8" + ) + pytester.makepyfile( + test_case=( + f'import os\ndef test_case() -> None:\n assert os.environ["{file_key}"] == {expected!r}\n' + ' assert os.environ["PYTEST_ENV_CASE"] == "original"\n' + ) + ) + with mock.patch.dict( + os.environ, + { + "PYTEST_ENV_CASE": "original", + "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1", + "PYTEST_PLUGINS": "pytest_env.plugin", + }, + clear=True, + ): + result: Final[pytest.RunResult] = pytester.runpytest("--pytest-env-verbose") + + result.assert_outcomes(passed=1) + action: Final[str] = "SET" if expected == "from_file" else "SKIP" + result.stdout.fnmatch_lines([f"*{action}*{file_key}={expected}*(from*.env*"])