Skip to content

Commit aa8acbc

Browse files
authored
Merge pull request #96 from CCPBioSim/95-improve-test-coverage-for-run
Additional Test Cases for `run.py`
2 parents b29d984 + 153226e commit aa8acbc

File tree

1 file changed

+305
-1
lines changed

1 file changed

+305
-1
lines changed

tests/test_CodeEntropy/test_run.py

Lines changed: 305 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import shutil
33
import tempfile
44
import unittest
5-
from unittest.mock import patch
5+
from unittest.mock import MagicMock, patch
6+
7+
import numpy as np
68

79
from CodeEntropy.run import RunManager
810

@@ -100,6 +102,308 @@ def test_create_job_folder_with_invalid_job_suffix(
100102
self.assertEqual(new_folder_path, expected_path)
101103
mock_makedirs.assert_called_once_with(expected_path, exist_ok=True)
102104

105+
def test_run_entropy_workflow(self):
106+
"""
107+
Test the run_entropy_workflow method to ensure it initializes and executes
108+
correctly with mocked dependencies.
109+
"""
110+
run_manager = RunManager("folder")
111+
run_manager._logging_config = MagicMock()
112+
run_manager._config_manager = MagicMock()
113+
run_manager._data_logger = MagicMock()
114+
run_manager.folder = self.test_dir
115+
116+
mock_logger = MagicMock()
117+
run_manager._logging_config.setup_logging.return_value = mock_logger
118+
119+
run_manager._config_manager.load_config.return_value = {
120+
"test_run": {
121+
"top_traj_file": ["/path/to/tpr", "/path/to/trr"],
122+
"selection_string": "all",
123+
"output_file": "output.json",
124+
"verbose": True,
125+
}
126+
}
127+
128+
mock_args = MagicMock()
129+
mock_args.output_file = "output.json"
130+
mock_args.verbose = True
131+
mock_args.top_traj_file = ["/path/to/tpr", "/path/to/trr"]
132+
mock_args.selection_string = "all"
133+
parser = run_manager._config_manager.setup_argparse.return_value
134+
parser.parse_known_args.return_value = (mock_args, [])
135+
136+
run_manager._config_manager.merge_configs.return_value = mock_args
137+
138+
mock_entropy_manager = MagicMock()
139+
with unittest.mock.patch(
140+
"CodeEntropy.run.EntropyManager", return_value=mock_entropy_manager
141+
), unittest.mock.patch("CodeEntropy.run.mda.Universe") as mock_universe:
142+
143+
run_manager.run_entropy_workflow()
144+
145+
mock_universe.assert_called_once_with("/path/to/tpr", ["/path/to/trr"])
146+
mock_entropy_manager.execute.assert_called_once()
147+
148+
def test_run_configuration_warning(self):
149+
"""
150+
Test that a warning is logged when the config entry is not a dictionary.
151+
"""
152+
run_manager = RunManager("folder")
153+
run_manager._logging_config = MagicMock()
154+
run_manager._config_manager = MagicMock()
155+
run_manager._data_logger = MagicMock()
156+
run_manager.folder = self.test_dir
157+
158+
mock_logger = MagicMock()
159+
run_manager._logging_config.setup_logging.return_value = mock_logger
160+
161+
run_manager._config_manager.load_config.return_value = {
162+
"invalid_run": "this_should_be_a_dict"
163+
}
164+
165+
mock_args = MagicMock()
166+
mock_args.output_file = "output.json"
167+
mock_args.verbose = False
168+
169+
parser = run_manager._config_manager.setup_argparse.return_value
170+
parser.parse_known_args.return_value = (mock_args, [])
171+
run_manager._config_manager.merge_configs.return_value = mock_args
172+
173+
run_manager.run_entropy_workflow()
174+
175+
mock_logger.warning.assert_called_with(
176+
"Run configuration for invalid_run is not a dictionary."
177+
)
178+
179+
def test_run_entropy_workflow_missing_traj_file(self):
180+
"""
181+
Test that a ValueError is raised when 'top_traj_file' is missing.
182+
"""
183+
run_manager = RunManager("folder")
184+
run_manager._logging_config = MagicMock()
185+
run_manager._config_manager = MagicMock()
186+
run_manager._data_logger = MagicMock()
187+
run_manager.folder = self.test_dir
188+
189+
mock_logger = MagicMock()
190+
run_manager._logging_config.setup_logging.return_value = mock_logger
191+
192+
run_manager._config_manager.load_config.return_value = {
193+
"test_run": {
194+
"top_traj_file": None,
195+
"output_file": "output.json",
196+
"verbose": False,
197+
}
198+
}
199+
200+
mock_args = MagicMock()
201+
mock_args.output_file = "output.json"
202+
mock_args.verbose = False
203+
mock_args.top_traj_file = None
204+
mock_args.selection_string = None
205+
206+
parser = run_manager._config_manager.setup_argparse.return_value
207+
parser.parse_known_args.return_value = (mock_args, [])
208+
run_manager._config_manager.merge_configs.return_value = mock_args
209+
210+
with self.assertRaisesRegex(ValueError, "Missing 'top_traj_file' argument."):
211+
run_manager.run_entropy_workflow()
212+
213+
def test_run_entropy_workflow_missing_selection_string(self):
214+
"""
215+
Test that a ValueError is raised when 'selection_string' is missing.
216+
"""
217+
run_manager = RunManager("folder")
218+
run_manager._logging_config = MagicMock()
219+
run_manager._config_manager = MagicMock()
220+
run_manager._data_logger = MagicMock()
221+
run_manager.folder = self.test_dir
222+
223+
mock_logger = MagicMock()
224+
run_manager._logging_config.setup_logging.return_value = mock_logger
225+
226+
run_manager._config_manager.load_config.return_value = {
227+
"test_run": {
228+
"top_traj_file": ["/path/to/tpr", "/path/to/trr"],
229+
"output_file": "output.json",
230+
"verbose": False,
231+
}
232+
}
233+
234+
mock_args = MagicMock()
235+
mock_args.output_file = "output.json"
236+
mock_args.verbose = False
237+
mock_args.top_traj_file = ["/path/to/tpr", "/path/to/trr"]
238+
mock_args.selection_string = None
239+
240+
parser = run_manager._config_manager.setup_argparse.return_value
241+
parser.parse_known_args.return_value = (mock_args, [])
242+
run_manager._config_manager.merge_configs.return_value = mock_args
243+
244+
with self.assertRaisesRegex(ValueError, "Missing 'selection_string' argument."):
245+
run_manager.run_entropy_workflow()
246+
247+
@patch("CodeEntropy.run.AnalysisFromFunction")
248+
@patch("CodeEntropy.run.mda.Merge")
249+
def test_new_U_select_frame(self, MockMerge, MockAnalysisFromFunction):
250+
# Mock Universe and its components
251+
mock_universe = MagicMock()
252+
mock_trajectory = MagicMock()
253+
mock_trajectory.__len__.return_value = 10
254+
mock_universe.trajectory = mock_trajectory
255+
256+
mock_select_atoms = MagicMock()
257+
mock_universe.select_atoms.return_value = mock_select_atoms
258+
259+
# Mock AnalysisFromFunction results for coordinates, forces, and dimensions
260+
coords = np.random.rand(10, 100, 3)
261+
forces = np.random.rand(10, 100, 3)
262+
dims = np.random.rand(10, 3)
263+
264+
mock_coords_analysis = MagicMock()
265+
mock_coords_analysis.run.return_value.results = {"timeseries": coords}
266+
267+
mock_forces_analysis = MagicMock()
268+
mock_forces_analysis.run.return_value.results = {"timeseries": forces}
269+
270+
mock_dims_analysis = MagicMock()
271+
mock_dims_analysis.run.return_value.results = {"timeseries": dims}
272+
273+
# Set the side effects for the three AnalysisFromFunction calls
274+
MockAnalysisFromFunction.side_effect = [
275+
mock_coords_analysis,
276+
mock_forces_analysis,
277+
mock_dims_analysis,
278+
]
279+
280+
# Mock the merge operation
281+
mock_merged_universe = MagicMock()
282+
MockMerge.return_value = mock_merged_universe
283+
284+
run_manager = RunManager("folder")
285+
result = run_manager.new_U_select_frame(mock_universe)
286+
287+
mock_universe.select_atoms.assert_called_once_with("all", updating=True)
288+
MockMerge.assert_called_once_with(mock_select_atoms)
289+
290+
# Ensure the 'load_new' method was called with the correct arguments
291+
mock_merged_universe.load_new.assert_called_once()
292+
args, kwargs = mock_merged_universe.load_new.call_args
293+
294+
# Assert that the arrays are passed correctly
295+
np.testing.assert_array_equal(args[0], coords)
296+
np.testing.assert_array_equal(kwargs["forces"], forces)
297+
np.testing.assert_array_equal(kwargs["dimensions"], dims)
298+
299+
# Check if format was included in the kwargs
300+
self.assertIn("format", kwargs)
301+
302+
# Ensure the result is the mock merged universe
303+
self.assertEqual(result, mock_merged_universe)
304+
305+
@patch("CodeEntropy.run.AnalysisFromFunction")
306+
@patch("CodeEntropy.run.mda.Merge")
307+
def test_new_U_select_atom(self, MockMerge, MockAnalysisFromFunction):
308+
# Mock Universe and its components
309+
mock_universe = MagicMock()
310+
mock_select_atoms = MagicMock()
311+
mock_universe.select_atoms.return_value = mock_select_atoms
312+
313+
# Mock AnalysisFromFunction results for coordinates, forces, and dimensions
314+
coords = np.random.rand(10, 100, 3)
315+
forces = np.random.rand(10, 100, 3)
316+
dims = np.random.rand(10, 3)
317+
318+
mock_coords_analysis = MagicMock()
319+
mock_coords_analysis.run.return_value.results = {"timeseries": coords}
320+
321+
mock_forces_analysis = MagicMock()
322+
mock_forces_analysis.run.return_value.results = {"timeseries": forces}
323+
324+
mock_dims_analysis = MagicMock()
325+
mock_dims_analysis.run.return_value.results = {"timeseries": dims}
326+
327+
# Set the side effects for the three AnalysisFromFunction calls
328+
MockAnalysisFromFunction.side_effect = [
329+
mock_coords_analysis,
330+
mock_forces_analysis,
331+
mock_dims_analysis,
332+
]
333+
334+
# Mock the merge operation
335+
mock_merged_universe = MagicMock()
336+
MockMerge.return_value = mock_merged_universe
337+
338+
run_manager = RunManager("folder")
339+
result = run_manager.new_U_select_atom(
340+
mock_universe, select_string="resid 1-10"
341+
)
342+
343+
mock_universe.select_atoms.assert_called_once_with("resid 1-10", updating=True)
344+
MockMerge.assert_called_once_with(mock_select_atoms)
345+
346+
# Ensure the 'load_new' method was called with the correct arguments
347+
mock_merged_universe.load_new.assert_called_once()
348+
args, kwargs = mock_merged_universe.load_new.call_args
349+
350+
# Assert that the arrays are passed correctly
351+
np.testing.assert_array_equal(args[0], coords)
352+
np.testing.assert_array_equal(kwargs["forces"], forces)
353+
np.testing.assert_array_equal(kwargs["dimensions"], dims)
354+
355+
# Check if format was included in the kwargs
356+
self.assertIn("format", kwargs)
357+
358+
# Ensure the result is the mock merged universe
359+
self.assertEqual(result, mock_merged_universe)
360+
361+
@patch("CodeEntropy.run.pickle.dump")
362+
@patch("CodeEntropy.run.open", create=True)
363+
def test_write_universe(self, mock_open, mock_pickle_dump):
364+
# Mock Universe
365+
mock_universe = MagicMock()
366+
367+
# Mock the file object returned by open
368+
mock_file = MagicMock()
369+
mock_open.return_value = mock_file
370+
371+
run_manager = RunManager("folder")
372+
result = run_manager.write_universe(mock_universe, name="test_universe")
373+
374+
mock_open.assert_called_once_with("test_universe.pkl", "wb")
375+
376+
# Ensure pickle.dump() was called
377+
mock_pickle_dump.assert_called_once_with(mock_universe, mock_file)
378+
379+
# Ensure the method returns the correct filename
380+
self.assertEqual(result, "test_universe")
381+
382+
@patch("CodeEntropy.run.pickle.load")
383+
@patch("CodeEntropy.run.open", create=True)
384+
def test_read_universe(self, mock_open, mock_pickle_load):
385+
# Mock the file object returned by open
386+
mock_file = MagicMock()
387+
mock_open.return_value = mock_file
388+
389+
# Mock Universe to return when pickle.load is called
390+
mock_universe = MagicMock()
391+
mock_pickle_load.return_value = mock_universe
392+
393+
# Path to the mock file
394+
path = "test_universe.pkl"
395+
396+
run_manager = RunManager("folder")
397+
result = run_manager.read_universe(path)
398+
399+
mock_open.assert_called_once_with(path, "rb")
400+
401+
# Ensure pickle.load() was called with the mock file object
402+
mock_pickle_load.assert_called_once_with(mock_file)
403+
404+
# Ensure the method returns the correct mock universe
405+
self.assertEqual(result, mock_universe)
406+
103407

104408
if __name__ == "__main__":
105409
unittest.main()

0 commit comments

Comments
 (0)