Skip to content

Commit 1d8bc75

Browse files
committed
Refactor test structure for improved modularity and dynamic testing:
- Moved tests to individual files for better organization - Implemented temporary files and directories for dynamic testing - Integrated tests/data files for ease of use - Included an __init__.py file, that is empty, to allow for the import of the data directory as a module
1 parent e04dad2 commit 1d8bc75

3 files changed

Lines changed: 312 additions & 233 deletions

File tree

tests/data/__init__.py

Whitespace-only changes.
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import argparse
2+
import os
3+
import shutil
4+
import tempfile
5+
import unittest
6+
from unittest.mock import MagicMock, mock_open, patch
7+
8+
import tests.data as data
9+
from CodeEntropy.config.arg_config_manager import ConfigManager
10+
from CodeEntropy.main_mcc import main
11+
12+
13+
class test_arg_config_manager(unittest.TestCase):
14+
"""
15+
Unit tests for the ConfigManager.
16+
"""
17+
18+
def setUp(self):
19+
"""
20+
Setup test data and output directories.
21+
"""
22+
self.test_data_dir = os.path.dirname(data.__file__)
23+
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
24+
self.config_file = os.path.join(self.test_dir, "config.yaml")
25+
26+
# Create a mock config file
27+
with patch("builtins.open", new_callable=mock_open) as mock_file:
28+
self.setup_file(mock_file)
29+
with open(self.config_file, "w") as f:
30+
f.write(mock_file.return_value.read())
31+
32+
# Change to test directory
33+
self._orig_dir = os.getcwd()
34+
os.chdir(self.test_dir)
35+
36+
def tearDown(self):
37+
"""
38+
Clean up after each test.
39+
"""
40+
os.chdir(self._orig_dir)
41+
if os.path.exists(self.test_dir):
42+
shutil.rmtree(self.test_dir)
43+
44+
def list_data_files(self):
45+
"""
46+
List all files in the test data directory.
47+
"""
48+
return os.listdir(self.test_data_dir)
49+
50+
def setup_file(self, mock_file):
51+
"""
52+
Mock the contents of a configuration file.
53+
"""
54+
mock_file.return_value = mock_open(
55+
read_data="--- \n \nrun1:\n "
56+
"top_traj_file: ['/path/to/tpr', '/path/to/trr']\n "
57+
"selection_string: 'all'\n "
58+
"start: 0\n "
59+
"end: -1\n "
60+
"step: 1\n "
61+
"bin_width: 30\n "
62+
"tempra: 298.0\n "
63+
"verbose: False\n "
64+
"thread: 1\n "
65+
"outfile: 'outfile.out'\n "
66+
"resfile: 'res_outfile.out'\n "
67+
"mout: null\n "
68+
"force_partitioning: 0.5\n "
69+
"waterEntropy: False"
70+
).return_value
71+
72+
@patch("builtins.open", new_callable=mock_open)
73+
@patch("os.path.exists", return_value=True)
74+
def test_load_config(self, mock_exists, mock_file):
75+
"""
76+
Test loading a valid configuration file.
77+
"""
78+
arg_config = ConfigManager()
79+
self.setup_file(mock_file)
80+
config = arg_config.load_config(self.config_file)
81+
self.assertIn("run1", config)
82+
self.assertEqual(
83+
config["run1"]["top_traj_file"], ["/path/to/tpr", "/path/to/trr"]
84+
)
85+
86+
@patch("builtins.open", side_effect=FileNotFoundError)
87+
def test_load_config_file_not_found(self, mock_file):
88+
"""
89+
Test loading a configuration file that does not exist.
90+
"""
91+
arg_config = ConfigManager()
92+
with self.assertRaises(FileNotFoundError):
93+
arg_config.load_config(self.config_file)
94+
95+
@patch.object(ConfigManager, "load_config", return_value=None)
96+
def test_no_cli_no_yaml(self, mock_load_config):
97+
"""Test behavior when no CLI arguments and no YAML file are provided."""
98+
with self.assertRaises(ValueError) as context:
99+
main()
100+
self.assertEqual(
101+
str(context.exception),
102+
"No configuration file found, and no CLI arguments were provided.",
103+
)
104+
105+
def test_invalid_run_config_type(self):
106+
"""
107+
Test that passing an invalid type for run_config raises a TypeError.
108+
"""
109+
arg_config = ConfigManager()
110+
args = MagicMock()
111+
invalid_configs = ["string", 123, 3.14, ["list"], {("tuple_key",): "value"}]
112+
113+
for invalid in invalid_configs:
114+
with self.assertRaises(TypeError):
115+
arg_config.merge_configs(args, invalid)
116+
117+
@patch(
118+
"argparse.ArgumentParser.parse_args",
119+
return_value=MagicMock(
120+
top_traj_file=["/path/to/tpr", "/path/to/trr"],
121+
selection_string="all",
122+
start=0,
123+
end=-1,
124+
step=1,
125+
bin_width=30,
126+
tempra=298.0,
127+
verbose=False,
128+
thread=1,
129+
outfile="outfile.out",
130+
resfile="res_outfile.out",
131+
mout=None,
132+
force_partitioning=0.5,
133+
waterEntropy=False,
134+
),
135+
)
136+
def test_setup_argparse(self, mock_args):
137+
"""
138+
Test parsing command-line arguments.
139+
"""
140+
arg_config = ConfigManager()
141+
parser = arg_config.setup_argparse()
142+
args = parser.parse_args()
143+
self.assertEqual(args.top_traj_file, ["/path/to/tpr", "/path/to/trr"])
144+
self.assertEqual(args.selection_string, "all")
145+
146+
def test_cli_overrides_defaults(self):
147+
"""
148+
Test if CLI parameters override default values.
149+
"""
150+
arg_config = ConfigManager()
151+
parser = arg_config.setup_argparse()
152+
args = parser.parse_args(
153+
["--top_traj_file", "/cli/path", "--selection_string", "cli_value"]
154+
)
155+
self.assertEqual(args.top_traj_file, ["/cli/path"])
156+
self.assertEqual(args.selection_string, "cli_value")
157+
158+
def test_yaml_overrides_defaults(self):
159+
"""
160+
Test if YAML parameters override default values.
161+
"""
162+
run_config = {"top_traj_file": ["/yaml/path"], "selection_string": "yaml_value"}
163+
args = argparse.Namespace()
164+
arg_config = ConfigManager()
165+
merged_args = arg_config.merge_configs(args, run_config)
166+
self.assertEqual(merged_args.top_traj_file, ["/yaml/path"])
167+
self.assertEqual(merged_args.selection_string, "yaml_value")
168+
169+
def test_cli_overrides_yaml(self):
170+
"""
171+
Test if CLI parameters override YAML parameters correctly.
172+
"""
173+
arg_config = ConfigManager()
174+
parser = arg_config.setup_argparse()
175+
args = parser.parse_args(
176+
["--top_traj_file", "/cli/path", "--selection_string", "cli_value"]
177+
)
178+
run_config = {"top_traj_file": ["/yaml/path"], "selection_string": "yaml_value"}
179+
merged_args = arg_config.merge_configs(args, run_config)
180+
self.assertEqual(merged_args.top_traj_file, ["/cli/path"])
181+
self.assertEqual(merged_args.selection_string, "cli_value")
182+
183+
def test_merge_configs(self):
184+
"""
185+
Test merging default arguments with a run configuration.
186+
"""
187+
arg_config = ConfigManager()
188+
args = MagicMock(
189+
top_traj_file=None,
190+
selection_string=None,
191+
start=None,
192+
end=None,
193+
step=None,
194+
bin_width=None,
195+
tempra=None,
196+
verbose=None,
197+
thread=None,
198+
outfile=None,
199+
resfile=None,
200+
mout=None,
201+
force_partitioning=None,
202+
waterEntropy=None,
203+
)
204+
run_config = {
205+
"top_traj_file": ["/path/to/tpr", "/path/to/trr"],
206+
"selection_string": "all",
207+
"start": 0,
208+
"end": -1,
209+
"step": 1,
210+
"bin_width": 30,
211+
"tempra": 298.0,
212+
"verbose": False,
213+
"thread": 1,
214+
"outfile": "outfile.out",
215+
"resfile": "res_outfile.out",
216+
"mout": None,
217+
"force_partitioning": 0.5,
218+
"waterEntropy": False,
219+
}
220+
merged_args = arg_config.merge_configs(args, run_config)
221+
self.assertEqual(merged_args.top_traj_file, ["/path/to/tpr", "/path/to/trr"])
222+
self.assertEqual(merged_args.selection_string, "all")
223+
224+
@patch("argparse.ArgumentParser.parse_args")
225+
def test_default_values(self, mock_parse_args):
226+
"""
227+
Test if argument parser assigns default values correctly.
228+
"""
229+
arg_config = ConfigManager()
230+
mock_parse_args.return_value = MagicMock(
231+
top_traj_file=["example.top", "example.traj"]
232+
)
233+
parser = arg_config.setup_argparse()
234+
args = parser.parse_args()
235+
self.assertEqual(args.top_traj_file, ["example.top", "example.traj"])
236+
237+
@patch(
238+
"argparse.ArgumentParser.parse_args", return_value=MagicMock(top_traj_file=None)
239+
)
240+
def test_missing_required_arguments(self, mock_args):
241+
"""
242+
Test behavior when required arguments are missing.
243+
"""
244+
arg_config = ConfigManager()
245+
parser = arg_config.setup_argparse()
246+
args = parser.parse_args()
247+
with self.assertRaises(ValueError):
248+
if not args.top_traj_file:
249+
raise ValueError(
250+
"The 'top_traj_file' argument is required but not provided."
251+
)
252+
253+
def test_invalid_argument_type(self):
254+
"""
255+
Test handling of invalid argument types.
256+
"""
257+
arg_config = ConfigManager()
258+
parser = arg_config.setup_argparse()
259+
with self.assertRaises(SystemExit):
260+
parser.parse_args(["--start", "invalid"])
261+
262+
@patch(
263+
"argparse.ArgumentParser.parse_args", return_value=MagicMock(start=-1, end=-10)
264+
)
265+
def test_edge_case_argument_values(self, mock_args):
266+
"""
267+
Test parsing of edge case values.
268+
"""
269+
arg_config = ConfigManager()
270+
parser = arg_config.setup_argparse()
271+
args = parser.parse_args()
272+
self.assertEqual(args.start, -1)
273+
self.assertEqual(args.end, -10)
274+
275+
@patch("builtins.open", new_callable=mock_open, read_data="--- \n")
276+
@patch("os.path.exists", return_value=True)
277+
def test_empty_yaml_config(self, mock_exists, mock_file):
278+
"""
279+
Test behavior when an empty YAML file is provided.
280+
Should use defaults or raise an appropriate error.
281+
"""
282+
283+
arg_config = ConfigManager()
284+
285+
config = arg_config.load_config(self.config_file)
286+
287+
self.assertIsInstance(config, dict)
288+
self.assertEqual(config, {})
289+
290+
291+
if __name__ == "__main__":
292+
unittest.main()

0 commit comments

Comments
 (0)