Skip to content

Commit

Permalink
Merge pull request #31 from glrs/unittests
Browse files Browse the repository at this point in the history
Add unittests
  • Loading branch information
glrs authored Dec 12, 2024
2 parents 5268a52 + 17e5638 commit 4b11ffa
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 16 deletions.
33 changes: 30 additions & 3 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ def test_load_realm_class_module_not_found(self, mock_import_module):

@patch("importlib.import_module")
def test_load_realm_class_attribute_error(self, mock_import_module):
# Module exists but class does not
mock_module = MagicMock()
# Creating a mock module with no attributes allowed
# means any attribute access raises AttributeError.
mock_module = MagicMock(spec=[])

mock_import_module.return_value = mock_module

module_path = "some.module.MissingClass"
result = YggdrasilUtilities.load_realm_class(module_path)

self.assertIsNone(result)
mock_import_module.assert_called_with("some.module")

Expand Down Expand Up @@ -93,6 +94,22 @@ def test_load_module_import_error(self, mock_import_module):
self.assertIsNone(result)
mock_import_module.assert_called_with("nonexistent.module")

@patch("importlib.import_module")
def test_load_realm_class_caching(self, mock_import_module):
# First call: loads and caches the class
mock_module = MagicMock()
mock_import_module.return_value = mock_module
module_path = "some.module.ExistingClass"
first_result = YggdrasilUtilities.load_realm_class(module_path)
self.assertIsNotNone(first_result)

# Second call: should return the cached class without calling import_module again
second_result = YggdrasilUtilities.load_realm_class(module_path)
self.assertIs(first_result, second_result)
mock_import_module.assert_called_once_with(
"some.module"
) # Confirm only called once

def test_get_path_file_exists(self):
# Create a dummy config file
file_name = "config.yaml"
Expand Down Expand Up @@ -216,6 +233,16 @@ def test_get_path_with_absolute_file_name(self):
result = YggdrasilUtilities.get_path(file_name)
self.assertIsNone(result) # Should not allow absolute paths

@patch.object(YggdrasilUtilities, "CONFIG_DIR", Path("/some/base/path"))
def test_get_path_resolve_error(self):
# Mock the config_file.resolve() call to raise an Exception
# to trigger the exception block in get_path
with patch(
"lib.core_utils.common.Path.resolve", side_effect=Exception("Resolve error")
):
result = YggdrasilUtilities.get_path("somefile")
self.assertIsNone(result)


if __name__ == "__main__":
unittest.main()
23 changes: 13 additions & 10 deletions tests/test_config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ def test_config_immutable(self):
# Test that the configuration data is immutable
self.config_loader._config = types.MappingProxyType(self.mock_config_data)
with self.assertRaises(TypeError):
original_dict = self.mock_config_data
with self.assertRaises(TypeError):
original_dict["key1"] = "new_value"
self.config_loader._config["key1"] = "new_value" # type: ignore

def test_load_config_type_error(self):
# Test handling of TypeError during json.load
Expand Down Expand Up @@ -132,18 +130,23 @@ def test_config_manager_instance(self):
self.assertIsInstance(config_manager, ConfigLoader)

def test_configs_loaded(self):
# Test that configs are loaded when the module is imported
with patch("lib.core_utils.config_loader.Ygg.get_path") as mock_get_path, patch(
"builtins.open", mock_open(read_data=self.mock_config_json)
with patch(
"lib.core_utils.config_loader.config_manager.load_config",
return_value=types.MappingProxyType(self.mock_config_data),
):
mock_get_path.return_value = Path("/path/to/config.json")
# Reload the module to trigger the code at the module level
import sys

if "config_loader" in sys.modules:
del sys.modules["config_loader"]
if "lib.core_utils.config_loader" in sys.modules:
del sys.modules["lib.core_utils.config_loader"]

from lib.core_utils import config_loader

# Patch the configs directly
config_loader.configs = types.MappingProxyType(self.mock_config_data)

self.assertEqual(
config_loader.configs, types.MappingProxyType(self.mock_config_data)
)
self.assertEqual(
config_loader.configs, types.MappingProxyType(self.mock_config_data)
)
Expand Down
45 changes: 44 additions & 1 deletion tests/test_report_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,50 @@ def test_transfer_report_general_exception(self, mock_subprocess_run, mock_confi
mock_logging.error.assert_any_call(
"Unexpected error during report transfer: Unexpected error"
)
mock_logging.error.assert_any_call("RSYNC output: ")
mock_logging.error.assert_any_call(
"RSYNC output: No output available due to early error."
)

@patch("lib.module_utils.report_transfer.configs")
@patch("lib.module_utils.report_transfer.subprocess.run")
@patch("lib.module_utils.report_transfer.logging")
def test_transfer_report_general_exception_with_result(
self, mock_logging, mock_subprocess_run, mock_configs
):
# Set up a valid config
mock_configs.__getitem__.return_value = {
"server": self.server,
"user": self.user,
"destination": self.remote_dir_base,
"ssh_key": self.ssh_key,
}

# Mock a successful subprocess run
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "Mocked RSYNC output"
mock_subprocess_run.return_value = mock_result

# Make logging.info raise an exception to simulate an error after success
def info_side_effect(*args, **kwargs):
raise Exception("Logging info error")

mock_logging.info.side_effect = info_side_effect

# Call the function
result = transfer_report(self.report_path, self.project_id, self.sample_id)

# Assert that the result is False because the exception should cause failure
self.assertFalse(result)

# Check that the unexpected error was logged
# The code logs: "Unexpected error during report transfer: Logging info error"
mock_logging.error.assert_any_call(
"Unexpected error during report transfer: Logging info error"
)

# Check that the RSYNC output was logged
mock_logging.error.assert_any_call("RSYNC output: Mocked RSYNC output")

@patch("lib.module_utils.report_transfer.configs")
@patch("lib.module_utils.report_transfer.subprocess.run")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_slurm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,12 @@ def test_generate_slurm_script_template_syntax_error(self, mock_file, mock_path)
def test_generate_slurm_script_invalid_template_path_type(self):
# Test with invalid type for template_fpath
with self.assertRaises(TypeError):
generate_slurm_script(self.args_dict, None, self.output_fpath)
generate_slurm_script(self.args_dict, None, self.output_fpath) # type: ignore

def test_generate_slurm_script_invalid_output_path_type(self):
# Test with invalid type for output_fpath
with self.assertRaises(TypeError):
generate_slurm_script(self.args_dict, self.template_fpath, None)
generate_slurm_script(self.args_dict, self.template_fpath, None) # type: ignore

@patch("lib.module_utils.slurm_utils.Path")
@patch("builtins.open", new_callable=mock_open)
Expand Down

0 comments on commit 4b11ffa

Please sign in to comment.