Skip to content

Commit 472ed02

Browse files
authored
fix(config): clear markers overridden by unconditional set_env values (tox-dev#4077)
Clear a previous marker when a later unconditional `set_env` assignment or environment-file value overrides the variable. Otherwise, a false marker still suppresses the replacement value. Adds regression coverage for inline INI assignments and environment-file overrides in INI and TOML. Fixes tox-dev#4076. Validation: Python 3.14 full suite (8,153 passed, 12 skipped), Python 3.10 config suite (6,749 passed, 2 skipped), ty/mypy/pyrefly, pre-commit, package checks, and strict Sphinx build with visual changelog inspection. The full suite used a process-local PATH excluding the host's legacy Python 2 installation to avoid interpreter-discovery warnings in JSON output. Checks ran directly with the project's dependency groups to reuse local environments. - [x] ran the linter to address style issues (`pre-commit run --all-files --show-diff-on-failure`) - [x] wrote descriptive pull request text - [x] ensured there are test(s) validating the fix - [x] added news fragment in `docs/changelog` folder - [x] updated/extended the documentation (changelog)
1 parent 2f96e32 commit 472ed02

3 files changed

Lines changed: 31 additions & 0 deletions

File tree

docs/changelog/4076.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Clear obsolete markers when unconditional ``set_env`` values override conditional entries, including environment files.

src/tox/config/set_env.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def __init__( # ruff:ignore[complex-structure, too-many-branches]
7272
keys_after_file.add(key)
7373
if marker:
7474
self._markers[key] = Marker(marker)
75+
else:
76+
self._markers.pop(key, None)
7577

7678
def _parse_dict(self, raw: dict[str, str | SetEnvEntry]) -> None:
7779
keys_after_file: set[str] = set()
@@ -109,6 +111,7 @@ def use_replacer(self, value: Replacer, args: ConfigLoadArgs) -> None:
109111
for key, val in self._stream_env_file(filename, args):
110112
if key not in keys_after:
111113
self._raw[key] = val
114+
self._markers.pop(key, None)
112115

113116
def _stream_env_file(self, filename: str, args: ConfigLoadArgs) -> Iterator[tuple[str, str]]:
114117
# Our rules in the documentation, some upstream environment file rules (we follow mostly the docker one):

tests/config/test_set_env.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,33 @@ def test_set_env_escaped_semicolon() -> None:
513513
assert set_env.load("FOO") == "a;b"
514514

515515

516+
@pytest.mark.parametrize(
517+
("of_type", "config"),
518+
[
519+
pytest.param(
520+
"ini",
521+
"[testenv]\npackage=skip\nset_env=FOO=conditional; sys_platform == 'nonexistent'\n FOO=unconditional",
522+
id="inline",
523+
),
524+
pytest.param(
525+
"ini",
526+
"[testenv]\npackage=skip\nset_env=FOO=conditional; sys_platform == 'nonexistent'\n file|.env",
527+
id="ini-file",
528+
),
529+
pytest.param(
530+
"toml",
531+
'[env_run_base]\npackage="skip"\n'
532+
'set_env = {FOO={value="conditional", marker="sys_platform == \'nonexistent\'"}, file=".env"}',
533+
id="toml-file",
534+
),
535+
],
536+
)
537+
def test_set_env_unconditional_override(eval_set_env: EvalSetEnv, of_type: ConfigFileFormat, config: str) -> None:
538+
set_env = eval_set_env(config, of_type=of_type, extra_files={".env": "FOO=unconditional\n"})
539+
assert "FOO" in set_env
540+
assert set_env.load("FOO") == "unconditional"
541+
542+
516543
def test_set_env_marker_mixed(eval_set_env: EvalSetEnv) -> None:
517544
marker = f"sys_platform == '{sys.platform}'"
518545
config = (

0 commit comments

Comments
 (0)