Skip to content

Fix flaky global Hydra state leaking between tests (#574)#876

Open
martinez-hub wants to merge 1 commit into
mit-ll-responsible-ai:mainfrom
martinez-hub:fix/574-test-global-state-isolation
Open

Fix flaky global Hydra state leaking between tests (#574)#876
martinez-hub wants to merge 1 commit into
mit-ll-responsible-ai:mainfrom
martinez-hub:fix/574-test-global-state-isolation

Conversation

@martinez-hub

Copy link
Copy Markdown

Summary

Fixes #574 — the intermittent MissingConfigException: In 'hydra/config': Could not find 'hydra/sweeper/basic' failures in tests/test_launch/test_validation.py that surface as flaky, Python-version-correlated CI failures.

Root cause

The clean_store fixtures (in tests/conftest.py and tests/test_store.py) snapshot and restore Hydra's global ConfigStore. Hydra registers its plugin-provided configs (e.g. hydra/sweeper/basic) into that store lazily, upon first plugin discovery (triggered by the first compose/initialize).

If a fixture snapshots the store before discovery has occurred and then restores that snapshot on teardown, it strips those plugin configs from the global store — permanently, since Plugins caches discovery and won't re-register them. Any later test that composes the Hydra config (e.g. with return_hydra_config=True) then fails to find hydra/sweeper/basic.

This is order-dependent, which is exactly why it manifested as flaky failures that correlated with the Python version (the set/order of collected tests differs across versions — e.g. *py310* tests are excluded on 3.9).

This can be reproduced deterministically:

from copy import deepcopy
from hydra import compose, initialize
from hydra.core.config_store import ConfigStore

cs = ConfigStore.instance()
prev = deepcopy(cs.repo)                     # snapshot BEFORE discovery
with initialize(config_path=None, version_base="1.3"):  # triggers discovery
    pass
cs.repo = prev                               # restore -> strips hydra/sweeper/basic
cs.store(name="cfg", node={"a": 1})
with initialize(config_path=None, version_base="1.3"):
    compose(config_name="cfg", overrides=[], return_hydra_config=True)
# MissingConfigException: Could not find 'hydra/sweeper/basic'

Fix

Force Hydra plugin discovery (Plugins.instance().discover()) before snapshotting in both clean_store fixtures, so the snapshot includes the plugin configs and the restore preserves them.

This is a test-infrastructure fix — there is no user-facing change, so no changelog entry is added. (Note: the approach floated earlier in #574 of clearing Singleton._instances inside launch/hydra_main was intentionally avoided — Hydra's ConfigStore is itself stored in Singleton._instances, so clearing it would wipe user-registered configs, and it does not address the actual mechanism above.)

Regression test

tests/test_launch/test_global_state_isolation.py reproduces the triggering order deterministically in an isolated pytester subprocess, using the real clean_store fixture: the first test snapshots via clean_store and then triggers discovery; the second test then requires hydra/sweeper/basic. Verified to fail without this fix and pass with it.

Verification

  • Regression test: red before the fix, green after.
  • Full test suite passes on Python 3.9 + hydra-core 1.3.3 (the configuration that was failing in CI): 1605 passed.
  • ruff format --check and ruff check clean on all changed files.

The `clean_store` test fixtures (in tests/conftest.py and tests/test_store.py)
snapshot and restore Hydra's global ConfigStore. Hydra registers its
plugin-provided configs (e.g. `hydra/sweeper/basic`) into that store lazily,
upon first plugin discovery. When a fixture snapshots the store *before*
discovery occurs and then restores that snapshot on teardown, it strips those
plugin configs from the global store. Any later test that composes the Hydra
config (e.g. with `return_hydra_config=True`) then fails with
`MissingConfigException: Could not find 'hydra/sweeper/basic'`.

This is order-dependent, which is why it surfaced as flaky failures that
correlated with the Python version (test-collection order differs across
versions).

Force Hydra plugin discovery before snapshotting in both fixtures so the
snapshot includes these configs and the restore preserves them. Adds a
deterministic regression test that reproduces the triggering order in an
isolated pytest subprocess against the real `clean_store` fixture.

Closes mit-ll-responsible-ai#574
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broken global state between Hydra runs

1 participant