Skip to content

Commit 04424a8

Browse files
committed
refactored IMD import test to use a custom state manager
- testing imports of IMD/imdclient module is now isolated from other tests - testing different versions of imdclient and IMD.HAS_IMDCLIENT is now done in separate tests
1 parent 6634706 commit 04424a8

File tree

1 file changed

+81
-27
lines changed

1 file changed

+81
-27
lines changed

testsuite/MDAnalysisTests/coordinates/test_imd.py

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,52 @@
4444
)
4545

4646

47-
def test_IMDCLIENT_import(monkeypatch):
48-
backup = sys.modules.copy()
47+
class IMDModuleStateManager:
48+
"""Context manager to completely backup and restore imdclient/IMD module state.
49+
50+
We need a custom manager because IMD changes its own state (HAS_IMDCLIENT) when it is imported
51+
and we are going to manipulate the state of the imdclient module that IMD sees.
52+
"""
53+
54+
def __init__(self):
55+
self.original_modules = None
56+
self.imd_was_imported = False
57+
58+
def __enter__(self):
59+
# Backup sys.modules
60+
self.original_modules = sys.modules.copy()
61+
62+
# Check if IMD module was already imported
63+
self.imd_was_imported = "MDAnalysis.coordinates.IMD" in sys.modules
64+
65+
return self
66+
67+
def __exit__(self, exc_type, exc_val, exc_tb):
68+
# Restore sys.modules completely first
69+
sys.modules.clear()
70+
sys.modules.update(self.original_modules)
71+
72+
# If IMD module was originally imported, force a fresh reload to restore original state
73+
# This ensures that HAS_IMDCLIENT and other globals are recalculated with the real imdclient
74+
if self.imd_was_imported:
75+
# Remove the potentially corrupted IMD module
76+
sys.modules.pop("MDAnalysis.coordinates.IMD", None)
77+
# Fresh import will re-evaluate all globals
78+
import MDAnalysis.coordinates.IMD
4979

50-
try:
51-
module_name = "imdclient"
5280

53-
# Create mock modules
81+
class TestImport:
82+
"""Test imdclient import behavior and HAS_IMDCLIENT flag."""
83+
84+
def _setup_mock_imdclient(self, monkeypatch, version):
85+
"""Helper method to set up mock imdclient with specified version."""
86+
# Remove IMD and imdclient modules to force fresh import
87+
monkeypatch.delitem(
88+
sys.modules, "MDAnalysis.coordinates.IMD", raising=False
89+
)
90+
monkeypatch.delitem(sys.modules, "imdclient", raising=False)
91+
92+
module_name = "imdclient"
5493
mocked_module = ModuleType(module_name)
5594
IMDClient_module = ModuleType(f"{module_name}.IMDClient")
5695

@@ -59,7 +98,7 @@ class MockIMDClient:
5998

6099
IMDClient_module.IMDClient = MockIMDClient
61100
mocked_module.IMDClient = IMDClient_module
62-
mocked_module.__version__ = str(MIN_IMDCLIENT_VERSION)
101+
mocked_module.__version__ = version
63102

64103
utils_module = ModuleType(f"{module_name}.utils")
65104
utils_module.parse_host_port = lambda x: ("localhost", 12345)
@@ -71,33 +110,48 @@ class MockIMDClient:
71110
)
72111
monkeypatch.setitem(sys.modules, f"{module_name}.utils", utils_module)
73112

74-
sys.modules.pop("MDAnalysis.coordinates.IMD", None)
113+
return mocked_module
75114

76-
# check if imdclient is new enough
77-
import MDAnalysis.coordinates.IMD
115+
def test_has_minversion(self, monkeypatch):
116+
"""Test that HAS_IMDCLIENT is True when imdclient >= MIN_IMDCLIENT_VERSION."""
117+
with IMDModuleStateManager():
118+
self._setup_mock_imdclient(monkeypatch, str(MIN_IMDCLIENT_VERSION))
78119

79-
importlib.reload(MDAnalysis.coordinates.IMD)
80-
from MDAnalysis.coordinates.IMD import HAS_IMDCLIENT
120+
# Import and check HAS_IMDCLIENT with compatible version
121+
import MDAnalysis.coordinates.IMD
122+
from MDAnalysis.coordinates.IMD import HAS_IMDCLIENT
81123

82-
assert HAS_IMDCLIENT
124+
assert (
125+
HAS_IMDCLIENT
126+
), f"HAS_IMDCLIENT should be True with version {MIN_IMDCLIENT_VERSION}"
83127

84-
# check if imdclient version is too old
85-
mocked_module.__version__ = "0.0.0"
86-
importlib.reload(MDAnalysis.coordinates.IMD)
87-
from MDAnalysis.coordinates.IMD import HAS_IMDCLIENT
88-
from MDAnalysis.coordinates.IMD import IMDReader as IMDReader_NOClient
128+
def test_no_minversion(self, monkeypatch):
129+
"""Test that HAS_IMDCLIENT is False when imdclient version is too old."""
130+
with IMDModuleStateManager():
131+
self._setup_mock_imdclient(monkeypatch, "0.0.0")
89132

90-
assert not HAS_IMDCLIENT
133+
# Import and check HAS_IMDCLIENT with incompatible version
134+
import MDAnalysis.coordinates.IMD
135+
from MDAnalysis.coordinates.IMD import HAS_IMDCLIENT
91136

92-
# test initialization error
93-
with pytest.raises(
94-
ImportError, match="IMDReader requires the imdclient"
95-
):
96-
IMDReader_NOClient("imd://localhost:12345", n_atoms=5)
97-
finally:
98-
# Restore sys.modules to avoid side effects on other tests
99-
sys.modules.clear()
100-
sys.modules.update(backup)
137+
assert (
138+
not HAS_IMDCLIENT
139+
), "HAS_IMDCLIENT should be False with version 0.0.0"
140+
141+
def test_missing_ImportError(self, monkeypatch):
142+
"""Test that IMDReader raises ImportError when HAS_IMDCLIENT=False."""
143+
with IMDModuleStateManager():
144+
self._setup_mock_imdclient(monkeypatch, "0.0.0")
145+
146+
# Import with incompatible version (HAS_IMDCLIENT=False)
147+
import MDAnalysis.coordinates.IMD
148+
from MDAnalysis.coordinates.IMD import IMDReader
149+
150+
# IMDReader should raise ImportError when HAS_IMDCLIENT=False
151+
with pytest.raises(
152+
ImportError, match="IMDReader requires the imdclient"
153+
):
154+
IMDReader("imd://localhost:12345", n_atoms=5)
101155

102156

103157
@pytest.mark.skipif(not HAS_IMDCLIENT, reason="IMDClient not installed")

0 commit comments

Comments
 (0)