Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
901f511
ci: expand test matrix to include Windows and macOS runners
harryswift01 Sep 17, 2025
ed57f8f
include `.lock` files within `.gitignore` file
harryswift01 Sep 17, 2025
b1455b1
remove `macos-latest` from test matrix build
harryswift01 Sep 17, 2025
ed8fd41
remove `windows-os-latest` from test matrix build
harryswift01 Sep 17, 2025
a67fd9a
change from `ubuntu-latest` os to `22.04`
harryswift01 Sep 17, 2025
00e25d8
include `windows-2025` to the `project-ci.yaml` os matrix
harryswift01 Sep 17, 2025
d91d4e6
use `os.path.realpath()` to normalise paths within `test_run.py`
harryswift01 Sep 17, 2025
149dea3
remove `windows-25` and replace it with `macos-15` within `project-ci…
harryswift01 Sep 17, 2025
3d458a5
Restructure `setup()` and `teardown()` to a unified format:
harryswift01 Sep 19, 2025
297dd97
remove `macos-15` from `project-ci.yam` to avoid GitHub Runner issue …
harryswift01 Sep 19, 2025
113755c
Refactor test setup and fix Unicode issues in CLI tests:
harryswift01 Sep 22, 2025
97b98db
Fix `RunManager` job folder tests by using `os.path.realpath` for cro…
harryswift01 Sep 22, 2025
5fd7935
Normalize paths in `create_job_folder tests to fix` cross-platform Wi…
harryswift01 Sep 22, 2025
b51664d
Merge branch '159-expand-ci-testing' of https://github.com/CCPBioSim/…
harryswift01 Sep 22, 2025
67340c8
include `macos-15` within the `project-ci.yaml` to extend the testing…
harryswift01 Sep 22, 2025
761027d
use `os.path.normcase` and `os.path.realpath` to ensure compatability…
harryswift01 Sep 22, 2025
535ecbe
Updated documentation for multi-OS testing:
harryswift01 Sep 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/project-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ on:

jobs:
tests:
runs-on: ubuntu-24.04
timeout-minutes: 30
name: Run tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04, windows-2025, macos-15]
python-version: ["3.11", "3.12", "3.13"]
name: Run tests
steps:
- name: Checkout repo
uses: actions/checkout@v5.0.0
Expand All @@ -36,7 +36,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}

docs:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5.0.0
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ ENV/
*.csv
Example_output/
Example/data/*.csv
*.lock

# profraw files from LLVM? Unclear exactly what triggers this
# There are reports this comes from LLVM profiling, but also Xcode 9.
Expand Down
2 changes: 1 addition & 1 deletion docs/developer_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Clone the repository::

Install development dependencies::

pip install -e .[testing,docs,pre-commit]
pip install -e ".[testing,docs,pre-commit]"

Running Tests
-------------
Expand Down
3 changes: 0 additions & 3 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ The program assumes the following default unit

Quick start guide
--------------------
.. Warning::

CodeEntropy has not been tested on Windows

A quick and easy way to get started is to use the command-line tool which you can run in bash by simply typing ``CodeEntropy``

Expand Down
23 changes: 4 additions & 19 deletions tests/test_CodeEntropy/test_arg_config_manager.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import argparse
import logging
import os
import shutil
import tempfile
import unittest
from unittest.mock import MagicMock, mock_open, patch

import tests.data as data
from CodeEntropy.config.arg_config_manager import ConfigManager
from CodeEntropy.main import main
from tests.test_CodeEntropy.test_base import BaseTestCase


class test_arg_config_manager(unittest.TestCase):
class TestArgConfigManager(BaseTestCase):
"""
Unit tests for the ConfigManager.
"""

def setUp(self):
"""
Setup test data and output directories.
"""
super().setUp()

self.test_data_dir = os.path.dirname(data.__file__)
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
self.config_file = os.path.join(self.test_dir, "config.yaml")

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

# Change to test directory
self._orig_dir = os.getcwd()
os.chdir(self.test_dir)

def tearDown(self):
"""
Clean up after each test.
"""
os.chdir(self._orig_dir)
if os.path.exists(self.test_dir):
shutil.rmtree(self.test_dir)

def list_data_files(self):
"""
List all files in the test data directory.
Expand Down
45 changes: 45 additions & 0 deletions tests/test_CodeEntropy/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os
import shutil
import tempfile
import unittest


class BaseTestCase(unittest.TestCase):
"""
Base test case class for cross-platform unit tests.

Provides:
1. A unique temporary directory for each test to avoid filesystem conflicts.
2. Automatic restoration of the working directory after each test.
3. Prepares a logs folder path for tests that need logging configuration.
"""

def setUp(self):
"""
Prepare the test environment before each test method runs.

Actions performed:
1. Creates a unique temporary directory for the test.
2. Creates a 'logs' subdirectory within the temp directory.
3. Changes the current working directory to the temporary directory.
"""
# Create a unique temporary test directory
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
self.logs_path = os.path.join(self.test_dir, "logs")
os.makedirs(self.logs_path, exist_ok=True)

self._orig_dir = os.getcwd()
os.chdir(self.test_dir)

def tearDown(self):
"""
Clean up the test environment after each test method runs.

Actions performed:
1. Restores the original working directory.
2. Deletes the temporary test directory along with all its contents.
"""
os.chdir(self._orig_dir)

if os.path.exists(self.test_dir):
shutil.rmtree(self.test_dir, ignore_errors=True)
29 changes: 4 additions & 25 deletions tests/test_CodeEntropy/test_data_logger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import json
import os
import shutil
import tempfile
import unittest

import numpy as np
Expand All @@ -10,38 +7,20 @@
from CodeEntropy.config.data_logger import DataLogger
from CodeEntropy.config.logging_config import LoggingConfig
from CodeEntropy.main import main
from tests.test_CodeEntropy.test_base import BaseTestCase


class TestDataLogger(unittest.TestCase):
class TestDataLogger(BaseTestCase):
"""
Unit tests for the DataLogger class. These tests verify the
correct behavior of data logging, JSON export, and table
logging functionalities.
Unit tests for the DataLogger class.
"""

def setUp(self):
"""
Set up a temporary test environment before each test.
Creates a temporary directory and initializes a DataLogger instance.
"""
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
super().setUp()
self.code_entropy = main

self._orig_dir = os.getcwd()
os.chdir(self.test_dir)

self.logger = DataLogger()
self.output_file = "test_output.json"

def tearDown(self):
"""
Clean up the test environment after each test.
Removes the temporary directory and restores the original working directory.
"""
os.chdir(self._orig_dir)
if os.path.exists(self.test_dir):
shutil.rmtree(self.test_dir)

def test_init(self):
"""
Test that the DataLogger initializes with empty molecule and residue data lists.
Expand Down
58 changes: 24 additions & 34 deletions tests/test_CodeEntropy/test_entropy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import math
import os
import shutil
Expand All @@ -20,32 +21,21 @@
from CodeEntropy.levels import LevelManager
from CodeEntropy.main import main
from CodeEntropy.run import ConfigManager, RunManager
from tests.test_CodeEntropy.test_base import BaseTestCase


class TestEntropyManager(unittest.TestCase):
class TestEntropyManager(BaseTestCase):
"""
Unit tests for the functionality of EntropyManager.
Unit tests for EntropyManager.
"""

def setUp(self):
"""
Set up test environment.
"""
self.test_dir = tempfile.mkdtemp(prefix="CodeEntropy_")
super().setUp()
self.test_data_dir = os.path.dirname(data.__file__)
self.code_entropy = main

# Change to test directory
self._orig_dir = os.getcwd()
os.chdir(self.test_dir)

def tearDown(self):
"""
Clean up after each test.
"""
os.chdir(self._orig_dir)
if os.path.exists(self.test_dir):
shutil.rmtree(self.test_dir)
# Disable MDAnalysis and commands file logging entirely
logging.getLogger("MDAnalysis").handlers = [logging.NullHandler()]
logging.getLogger("commands").handlers = [logging.NullHandler()]

def test_execute_full_workflow(self):
# Setup universe and args
Expand All @@ -56,7 +46,7 @@ def test_execute_full_workflow(self):
args = MagicMock(
bin_width=0.1, temperature=300, selection_string="all", water_entropy=False
)
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -153,7 +143,7 @@ def test_execute_triggers_handle_water_entropy_minimal(self):
args = MagicMock(
bin_width=0.1, temperature=300, selection_string="all", water_entropy=True
)
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -279,7 +269,7 @@ def test_initialize_molecules(self):
args = MagicMock(
bin_width=0.1, temperature=300, selection_string="all", water_entropy=False
)
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -486,7 +476,7 @@ def test_get_reduced_universe_reduced(self, mock_args):
u = mda.Universe(tprfile, trrfile)

config_manager = ConfigManager()
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")

parser = config_manager.setup_argparse()
args = parser.parse_args()
Expand Down Expand Up @@ -524,7 +514,7 @@ def test_get_molecule_container(self, mock_args):

# Setup managers
config_manager = ConfigManager()
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")

parser = config_manager.setup_argparse()
args = parser.parse_args()
Expand Down Expand Up @@ -639,7 +629,7 @@ def test_process_vibrational_only_levels(self):

# Setup managers and arguments
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -751,7 +741,7 @@ def test_process_conformational_residue_level(self):

# Setup managers and arguments
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -1086,7 +1076,7 @@ def test_vibrational_entropy_init(self):
args.temperature = 300
args.selection_string = "all"

run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand All @@ -1111,7 +1101,7 @@ def test_frequency_calculation_0(self):
lambdas = [0]
temp = 298

run_manager = RunManager("mock_folder")
run_manager = RunManager("mock_folder/job001")

ve = VibrationalEntropy(
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
Expand All @@ -1131,7 +1121,7 @@ def test_frequency_calculation_positive(self):
temp = 298

# Create a mock RunManager and set return value for get_KT2J
run_manager = RunManager("mock_folder")
run_manager = RunManager("mock_folder/job001")

# Instantiate VibrationalEntropy with mocks
ve = VibrationalEntropy(
Expand Down Expand Up @@ -1273,7 +1263,7 @@ def test_vibrational_entropy_polymer_force(self):
temp = 298
highest_level = "yes"

run_manager = RunManager("mock_folder")
run_manager = RunManager("mock_folder/job001")
ve = VibrationalEntropy(
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
)
Expand Down Expand Up @@ -1303,7 +1293,7 @@ def test_vibrational_entropy_polymer_torque(self):
temp = 298
highest_level = "yes"

run_manager = RunManager("mock_folder")
run_manager = RunManager("mock_folder/job001")
ve = VibrationalEntropy(
run_manager, MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
)
Expand Down Expand Up @@ -1561,7 +1551,7 @@ def test_confirmational_entropy_init(self):
args.temperature = 300
args.selection_string = "all"

run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -1603,7 +1593,7 @@ def test_assign_conformation(self):

# Setup managers and arguments
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -1635,7 +1625,7 @@ def test_conformational_entropy_calculation(self):

# Setup managers and arguments
args = MagicMock(bin_width=0.1, temperature=300, selection_string="all")
run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down Expand Up @@ -1697,7 +1687,7 @@ def test_orientational_entropy_init(self):
args.temperature = 300
args.selection_string = "all"

run_manager = RunManager("temp_folder")
run_manager = RunManager("mock_folder/job001")
level_manager = LevelManager()
data_logger = DataLogger()
group_molecules = MagicMock()
Expand Down
Loading