44
44
)
45
45
46
46
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
49
79
50
- try :
51
- module_name = "imdclient"
52
80
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"
54
93
mocked_module = ModuleType (module_name )
55
94
IMDClient_module = ModuleType (f"{ module_name } .IMDClient" )
56
95
@@ -59,7 +98,7 @@ class MockIMDClient:
59
98
60
99
IMDClient_module .IMDClient = MockIMDClient
61
100
mocked_module .IMDClient = IMDClient_module
62
- mocked_module .__version__ = str ( MIN_IMDCLIENT_VERSION )
101
+ mocked_module .__version__ = version
63
102
64
103
utils_module = ModuleType (f"{ module_name } .utils" )
65
104
utils_module .parse_host_port = lambda x : ("localhost" , 12345 )
@@ -71,33 +110,48 @@ class MockIMDClient:
71
110
)
72
111
monkeypatch .setitem (sys .modules , f"{ module_name } .utils" , utils_module )
73
112
74
- sys . modules . pop ( "MDAnalysis.coordinates.IMD" , None )
113
+ return mocked_module
75
114
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 ))
78
119
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
81
123
82
- assert HAS_IMDCLIENT
124
+ assert (
125
+ HAS_IMDCLIENT
126
+ ), f"HAS_IMDCLIENT should be True with version { MIN_IMDCLIENT_VERSION } "
83
127
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" )
89
132
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
91
136
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 )
101
155
102
156
103
157
@pytest .mark .skipif (not HAS_IMDCLIENT , reason = "IMDClient not installed" )
0 commit comments