diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py index a0079bdd6..4c0822296 100644 --- a/tests/cli/conftest.py +++ b/tests/cli/conftest.py @@ -152,17 +152,24 @@ def plan_file(temp_dir): # Environment configuration for tests @pytest.fixture(autouse=True) -def test_environment(): - """Set up test environment variables""" +def test_environment(monkeypatch): + """Set up test environment variables. + + Both go through ``monkeypatch``. Autouse fixtures in the root conftest and in + this one create ``monkeypatch`` before this fixture, so its undo runs after the + restore below. Set straight into ``os.environ``, these values were what a test's + own ``monkeypatch.delenv`` or ``setenv`` recorded as the original, and its undo put + them back for the rest of the session (see ``test_conftest_env_isolation.py``). + """ original_env = os.environ.copy() # Set test environment variables - os.environ["FLUID_ENV"] = "test" - os.environ["FLUID_LOG_LEVEL"] = "DEBUG" + monkeypatch.setenv("FLUID_ENV", "test") + monkeypatch.setenv("FLUID_LOG_LEVEL", "DEBUG") yield - # Restore original environment + # Restore anything a test changed in os.environ directly os.environ.clear() os.environ.update(original_env) diff --git a/tests/cli/test_conftest_env_isolation.py b/tests/cli/test_conftest_env_isolation.py new file mode 100644 index 000000000..215644ac0 --- /dev/null +++ b/tests/cli/test_conftest_env_isolation.py @@ -0,0 +1,55 @@ +# Copyright 2024-2026 Agentics Transformation Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""``tests/cli/conftest.py::test_environment`` must not leak what it sets. + +It wrote ``FLUID_ENV`` and ``FLUID_LOG_LEVEL`` straight into ``os.environ`` and +restored a snapshot afterwards. Autouse fixtures in the root conftest and in this +directory's conftest create ``monkeypatch`` before it, so ``monkeypatch.undo()`` ran +after that restore. A test that called ``monkeypatch.delenv`` or ``monkeypatch.setenv`` +on either variable recorded the fixture's value as the original, and the undo put it +back for the rest of the session. ``test_cli_output_keeps_brackets.py`` delenvs every +``FLUID_*`` key. Later subprocess tests inherited ``FLUID_LOG_LEVEL=DEBUG``, and +``TestValidateStrict`` in ``tests/test_overlay_keyed_merge.py`` saw the loader log its +warning a second time. +""" + +from __future__ import annotations + +import os + +import pytest + +# Everything test_environment sets. +_FIXTURE_VARS = ("FLUID_ENV", "FLUID_LOG_LEVEL") + + +@pytest.fixture(scope="module", autouse=True) +def _nothing_outlives_the_module(): + # Module scope: torn down after every function-scoped fixture of the last test, + # monkeypatch's undo included, so this sees what the next module would inherit. + saved = {name: os.environ.pop(name, None) for name in _FIXTURE_VARS} + yield + leaked = {name: os.environ.pop(name) for name in _FIXTURE_VARS if name in os.environ} + for name, value in saved.items(): + if value is not None: + os.environ[name] = value + assert not leaked, f"{leaked} outlived this module's tests" + + +def test_a_test_may_delenv_what_the_fixture_set(monkeypatch): + assert (os.environ["FLUID_ENV"], os.environ["FLUID_LOG_LEVEL"]) == ("test", "DEBUG") + for name in _FIXTURE_VARS: + monkeypatch.delenv(name) + assert not any(name in os.environ for name in _FIXTURE_VARS) diff --git a/tests/test_overlay_keyed_merge.py b/tests/test_overlay_keyed_merge.py index c8d823b92..ddb9d7919 100644 --- a/tests/test_overlay_keyed_merge.py +++ b/tests/test_overlay_keyed_merge.py @@ -428,9 +428,11 @@ def _write_product(tmp_path: Path, version: str, overlay: Dict[str, Any]) -> Non PROD_BY_POSITION = {"builds": [{}, {"id": "transform", "description": "8 executors"}]} #: Environment variables a CLI subprocess here must not inherit: API keys, and -#: the debug level ``tests/cli/conftest.py`` sets, which some xdist workers keep. -#: At DEBUG the loader also logs a warning the command reports, and the tests -#: below count that the terminal shows it once. +#: debug switches, such as one a developer exported. (``tests/cli/conftest.py`` +#: used to leak its ``FLUID_LOG_LEVEL=DEBUG`` into later tests; see +#: ``tests/cli/test_conftest_env_isolation.py``.) At DEBUG the loader also logs a +#: warning the command reports, and the tests below count that the terminal shows +#: it once. _NOT_INHERITED = ("FLUID_LOG_LEVEL", "FLUID_DEBUG", "FLUID_SHOW_REGISTRATION")