Skip to content

Commit 3d458a5

Browse files
committed
Restructure setup() and teardown() to a unified format:
- move the `setup()` and `teardown()` functions to a `test_base.py` file to centrally manage the setup and teardown of files - use `logging.shutdown()` to ensure logging handlers are correctly closed and shutdown after use to avoid file IO issues within tests - ensure the correct format of `jobxxx` folders are being used within tests ti ensure consistency
1 parent 149dea3 commit 3d458a5

9 files changed

Lines changed: 106 additions & 178 deletions

tests/test_CodeEntropy/test_arg_config_manager.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import argparse
22
import logging
33
import os
4-
import shutil
5-
import tempfile
64
import unittest
75
from unittest.mock import MagicMock, mock_open, patch
86

97
import tests.data as data
108
from CodeEntropy.config.arg_config_manager import ConfigManager
119
from CodeEntropy.main import main
10+
from tests.test_CodeEntropy.test_base import BaseTestCase
1211

1312

14-
class test_arg_config_manager(unittest.TestCase):
13+
class TestArgConfigManager(BaseTestCase):
1514
"""
1615
Unit tests for the ConfigManager.
1716
"""
1817

1918
def setUp(self):
20-
"""
21-
Setup test data and output directories.
22-
"""
19+
super().setUp()
20+
2321
self.test_data_dir = os.path.dirname(data.__file__)
24-
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
2522
self.config_file = os.path.join(self.test_dir, "config.yaml")
2623

2724
# Create a mock config file
@@ -30,18 +27,6 @@ def setUp(self):
3027
with open(self.config_file, "w") as f:
3128
f.write(mock_file.return_value.read())
3229

33-
# Change to test directory
34-
self._orig_dir = os.getcwd()
35-
os.chdir(self.test_dir)
36-
37-
def tearDown(self):
38-
"""
39-
Clean up after each test.
40-
"""
41-
os.chdir(self._orig_dir)
42-
if os.path.exists(self.test_dir):
43-
shutil.rmtree(self.test_dir)
44-
4530
def list_data_files(self):
4631
"""
4732
List all files in the test data directory.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import logging
2+
import os
3+
import shutil
4+
import tempfile
5+
import unittest
6+
import uuid
7+
8+
9+
class BaseTestCase(unittest.TestCase):
10+
"""
11+
Base class for tests with cross-platform setup and teardown.
12+
Creates unique temporary directories and pre-creates expected log files.
13+
"""
14+
15+
def setUp(self):
16+
# Unique temporary test directory
17+
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
18+
self._orig_dir = os.getcwd()
19+
os.chdir(self.test_dir)
20+
21+
# Unique job folder + logs
22+
self.job_id = f"job_{uuid.uuid4().hex[:6]}"
23+
self.job_path = os.path.join(self.test_dir, self.job_id)
24+
self.logs_path = os.path.join(self.job_path, "logs")
25+
os.makedirs(self.logs_path, exist_ok=True)
26+
27+
# Pre-create log files
28+
for fname in ["mdanalysis.log", "program.log", "program.com"]:
29+
with open(os.path.join(self.logs_path, fname), "w") as f:
30+
f.write("")
31+
32+
def tearDown(self):
33+
# Shutdown logging and remove handlers (important for Windows)
34+
logging.shutdown()
35+
for handler in logging.root.handlers[:]:
36+
logging.root.removeHandler(handler)
37+
38+
# Restore working directory
39+
os.chdir(self._orig_dir)
40+
41+
# Remove temp directory (fail loudly if locked)
42+
if os.path.exists(self.test_dir):
43+
shutil.rmtree(self.test_dir, ignore_errors=False)

tests/test_CodeEntropy/test_data_logger.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import json
2-
import os
3-
import shutil
4-
import tempfile
52
import unittest
63

74
import numpy as np
@@ -10,38 +7,20 @@
107
from CodeEntropy.config.data_logger import DataLogger
118
from CodeEntropy.config.logging_config import LoggingConfig
129
from CodeEntropy.main import main
10+
from tests.test_CodeEntropy.test_base import BaseTestCase
1311

1412

15-
class TestDataLogger(unittest.TestCase):
13+
class TestDataLogger(BaseTestCase):
1614
"""
17-
Unit tests for the DataLogger class. These tests verify the
18-
correct behavior of data logging, JSON export, and table
19-
logging functionalities.
15+
Unit tests for the DataLogger class.
2016
"""
2117

2218
def setUp(self):
23-
"""
24-
Set up a temporary test environment before each test.
25-
Creates a temporary directory and initializes a DataLogger instance.
26-
"""
27-
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
19+
super().setUp()
2820
self.code_entropy = main
29-
30-
self._orig_dir = os.getcwd()
31-
os.chdir(self.test_dir)
32-
3321
self.logger = DataLogger()
3422
self.output_file = "test_output.json"
3523

36-
def tearDown(self):
37-
"""
38-
Clean up the test environment after each test.
39-
Removes the temporary directory and restores the original working directory.
40-
"""
41-
os.chdir(self._orig_dir)
42-
if os.path.exists(self.test_dir):
43-
shutil.rmtree(self.test_dir)
44-
4524
def test_init(self):
4625
"""
4726
Test that the DataLogger initializes with empty molecule and residue data lists.

tests/test_CodeEntropy/test_entropy.py

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,17 @@
2020
from CodeEntropy.levels import LevelManager
2121
from CodeEntropy.main import main
2222
from CodeEntropy.run import ConfigManager, RunManager
23+
from tests.test_CodeEntropy.test_base import BaseTestCase
2324

2425

25-
class TestEntropyManager(unittest.TestCase):
26+
class TestEntropyManager(BaseTestCase):
2627
"""
27-
Unit tests for the functionality of EntropyManager.
28+
Unit tests for EntropyManager.
2829
"""
2930

3031
def setUp(self):
31-
"""
32-
Set up test environment.
33-
"""
34-
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
32+
super().setUp()
3533
self.test_data_dir = os.path.dirname(data.__file__)
36-
self.code_entropy = main
37-
38-
# Change to test directory
39-
self._orig_dir = os.getcwd()
40-
os.chdir(self.test_dir)
41-
42-
def tearDown(self):
43-
"""
44-
Clean up after each test.
45-
"""
46-
os.chdir(self._orig_dir)
47-
if os.path.exists(self.test_dir):
48-
shutil.rmtree(self.test_dir)
4934

5035
def test_execute_full_workflow(self):
5136
# Setup universe and args
@@ -56,7 +41,7 @@ def test_execute_full_workflow(self):
5641
args = MagicMock(
5742
bin_width=0.1, temperature=300, selection_string="all", water_entropy=False
5843
)
59-
run_manager = RunManager("temp_folder")
44+
run_manager = RunManager("mock_folder/job001")
6045
level_manager = LevelManager()
6146
data_logger = DataLogger()
6247
group_molecules = MagicMock()
@@ -153,7 +138,7 @@ def test_execute_triggers_handle_water_entropy_minimal(self):
153138
args = MagicMock(
154139
bin_width=0.1, temperature=300, selection_string="all", water_entropy=True
155140
)
156-
run_manager = RunManager("temp_folder")
141+
run_manager = RunManager("mock_folder/job001")
157142
level_manager = LevelManager()
158143
data_logger = DataLogger()
159144
group_molecules = MagicMock()
@@ -279,7 +264,7 @@ def test_initialize_molecules(self):
279264
args = MagicMock(
280265
bin_width=0.1, temperature=300, selection_string="all", water_entropy=False
281266
)
282-
run_manager = RunManager("temp_folder")
267+
run_manager = RunManager("mock_folder/job001")
283268
level_manager = LevelManager()
284269
data_logger = DataLogger()
285270
group_molecules = MagicMock()
@@ -486,7 +471,7 @@ def test_get_reduced_universe_reduced(self, mock_args):
486471
u = mda.Universe(tprfile, trrfile)
487472

488473
config_manager = ConfigManager()
489-
run_manager = RunManager("temp_folder")
474+
run_manager = RunManager("mock_folder/job001")
490475

491476
parser = config_manager.setup_argparse()
492477
args = parser.parse_args()
@@ -524,7 +509,7 @@ def test_get_molecule_container(self, mock_args):
524509

525510
# Setup managers
526511
config_manager = ConfigManager()
527-
run_manager = RunManager("temp_folder")
512+
run_manager = RunManager("mock_folder/job001")
528513

529514
parser = config_manager.setup_argparse()
530515
args = parser.parse_args()
@@ -639,7 +624,7 @@ def test_process_vibrational_only_levels(self):
639624

640625
# Setup managers and arguments
641626
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
642-
run_manager = RunManager("temp_folder")
627+
run_manager = RunManager("mock_folder/job001")
643628
level_manager = LevelManager()
644629
data_logger = DataLogger()
645630
group_molecules = MagicMock()
@@ -751,7 +736,7 @@ def test_process_conformational_residue_level(self):
751736

752737
# Setup managers and arguments
753738
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
754-
run_manager = RunManager("temp_folder")
739+
run_manager = RunManager("mock_folder/job001")
755740
level_manager = LevelManager()
756741
data_logger = DataLogger()
757742
group_molecules = MagicMock()
@@ -1086,7 +1071,7 @@ def test_vibrational_entropy_init(self):
10861071
args.temperature = 300
10871072
args.selection_string = "all"
10881073

1089-
run_manager = RunManager("temp_folder")
1074+
run_manager = RunManager("mock_folder/job001")
10901075
level_manager = LevelManager()
10911076
data_logger = DataLogger()
10921077
group_molecules = MagicMock()
@@ -1111,7 +1096,7 @@ def test_frequency_calculation_0(self):
11111096
lambdas = [0]
11121097
temp = 298
11131098

1114-
run_manager = RunManager("mock_folder")
1099+
run_manager = RunManager("mock_folder/job001")
11151100

11161101
ve = VibrationalEntropy(
11171102
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
@@ -1131,7 +1116,7 @@ def test_frequency_calculation_positive(self):
11311116
temp = 298
11321117

11331118
# Create a mock RunManager and set return value for get_KT2J
1134-
run_manager = RunManager("mock_folder")
1119+
run_manager = RunManager("mock_folder/job001")
11351120

11361121
# Instantiate VibrationalEntropy with mocks
11371122
ve = VibrationalEntropy(
@@ -1273,7 +1258,7 @@ def test_vibrational_entropy_polymer_force(self):
12731258
temp = 298
12741259
highest_level = "yes"
12751260

1276-
run_manager = RunManager("mock_folder")
1261+
run_manager = RunManager("mock_folder/job001")
12771262
ve = VibrationalEntropy(
12781263
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
12791264
)
@@ -1303,7 +1288,7 @@ def test_vibrational_entropy_polymer_torque(self):
13031288
temp = 298
13041289
highest_level = "yes"
13051290

1306-
run_manager = RunManager("mock_folder")
1291+
run_manager = RunManager("mock_folder/job001")
13071292
ve = VibrationalEntropy(
13081293
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
13091294
)
@@ -1561,7 +1546,7 @@ def test_confirmational_entropy_init(self):
15611546
args.temperature = 300
15621547
args.selection_string = "all"
15631548

1564-
run_manager = RunManager("temp_folder")
1549+
run_manager = RunManager("mock_folder/job001")
15651550
level_manager = LevelManager()
15661551
data_logger = DataLogger()
15671552
group_molecules = MagicMock()
@@ -1603,7 +1588,7 @@ def test_assign_conformation(self):
16031588

16041589
# Setup managers and arguments
16051590
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
1606-
run_manager = RunManager("temp_folder")
1591+
run_manager = RunManager("mock_folder/job001")
16071592
level_manager = LevelManager()
16081593
data_logger = DataLogger()
16091594
group_molecules = MagicMock()
@@ -1635,7 +1620,7 @@ def test_conformational_entropy_calculation(self):
16351620

16361621
# Setup managers and arguments
16371622
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
1638-
run_manager = RunManager("temp_folder")
1623+
run_manager = RunManager("mock_folder/job001")
16391624
level_manager = LevelManager()
16401625
data_logger = DataLogger()
16411626
group_molecules = MagicMock()
@@ -1697,7 +1682,7 @@ def test_orientational_entropy_init(self):
16971682
args.temperature = 300
16981683
args.selection_string = "all"
16991684

1700-
run_manager = RunManager("temp_folder")
1685+
run_manager = RunManager("mock_folder/job001")
17011686
level_manager = LevelManager()
17021687
data_logger = DataLogger()
17031688
group_molecules = MagicMock()

tests/test_CodeEntropy/test_group_molecules.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
1-
import os
2-
import shutil
3-
import tempfile
41
import unittest
52
from unittest.mock import MagicMock
63

74
import numpy as np
85

96
from CodeEntropy.group_molecules import GroupMolecules
7+
from tests.test_CodeEntropy.test_base import BaseTestCase
108

119

12-
class TestMain(unittest.TestCase):
10+
class TestGroupMolecules(BaseTestCase):
1311
"""
14-
Unit tests for the functionality of GroupMolecules class.
12+
Unit tests for GroupMolecules.
1513
"""
1614

1715
def setUp(self):
18-
"""
19-
Set up a temporary directory as the working directory before each test.
20-
Initialize GroupMolecules instance.
21-
"""
22-
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
23-
self._orig_dir = os.getcwd()
24-
os.chdir(self.test_dir)
16+
super().setUp()
2517
self.group_molecules = GroupMolecules()
2618

27-
def tearDown(self):
28-
"""
29-
Clean up by removing the temporary directory and restoring the original working
30-
directory.
31-
"""
32-
os.chdir(self._orig_dir)
33-
shutil.rmtree(self.test_dir)
34-
3519
def test_by_none_returns_individual_groups(self):
3620
"""
3721
Test _by_none returns each molecule in its own group when grouping is 'each'.

0 commit comments

Comments
 (0)