Skip to content

Commit

Permalink
Merge pull request #482 from ACCESS-NRI/481-fix-payu-module-uniquenes…
Browse files Browse the repository at this point in the history
…s-check

Fix payu user module uniqueness checks
  • Loading branch information
jo-basevi authored Aug 14, 2024
2 parents 83001a2 + bfecc12 commit e611bcc
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 18 deletions.
45 changes: 27 additions & 18 deletions payu/envmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,8 @@ def setup_user_modules(user_modules, user_modulepaths):
previous_path = os.environ.get('PATH', '')

for modulefile in user_modules:
# Check module exists and there is not multiple available
output = run_module_cmd("avail --terse", modulefile).stderr

# Extract out the modulefiles available - strip out lines like:
# /apps/Modules/modulefiles:
modules = [line for line in output.strip().splitlines()
if not (line.startswith('/') and line.endswith(':'))]

# Modules are used for finding model executable paths - so check
# for unique module
if len(modules) > 1:
raise ValueError(
f"There are multiple modules available for {modulefile}:\n" +
f"{output}\n{MULTIPLE_MODULES_HELP}")
elif len(modules) == 0:
raise ValueError(
f"Module is not found: {modulefile}\n{MODULE_NOT_FOUND_HELP}"
)
# Check modulefile exists and is unique or has an exact match
check_modulefile(modulefile)

# Load module
module('load', modulefile)
Expand All @@ -191,6 +175,31 @@ def setup_user_modules(user_modules, user_modulepaths):
return (loaded_modules, paths)


def check_modulefile(modulefile: str) -> None:
"""Given a modulefile, check if modulefile exists, and there is
a unique modulefile available - e.g. if it's version is specified"""
output = run_module_cmd("avail --terse", modulefile).stderr

# Extract out the modulefiles available - strip out lines like:
# /apps/Modules/modulefiles:
modules_avail = [line for line in output.strip().splitlines()
if not (line.startswith('/') and line.endswith(':'))]

# Remove () from end of modulefiles if they exist, e.g. (default)
modules_avail = [mod.rsplit('(', 1)[0] for mod in modules_avail]

# Modules are used for finding model executable paths - so check
# for unique module, or an exact match for the modulefile name
if len(modules_avail) > 1 and modules_avail.count(modulefile) != 1:
raise ValueError(
f"There are multiple modules available for {modulefile}:\n" +
f"{output}\n{MULTIPLE_MODULES_HELP}")
elif len(modules_avail) == 0:
raise ValueError(
f"Module is not found: {modulefile}\n{MODULE_NOT_FOUND_HELP}"
)


def run_module_cmd(subcommand, *args):
"""Wrapper around subprocess module command that captures output"""
modulecmd = f"{os.environ['MODULESHOME']}/bin/modulecmd bash"
Expand Down
105 changes: 105 additions & 0 deletions test/test_envmod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest
from unittest.mock import patch

from payu.envmod import check_modulefile


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_unique(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0
"""
# Test runs without an error
check_modulefile('test-module/1.0.0')


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_without_version(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0
test-module/2.0.0
test-module/3.0.1
"""

# Expect an error raised
with pytest.raises(ValueError) as exc_info:
check_modulefile('test-module')
exc_info.value.startswith(
"There are multiple modules available for test-module"
)

# Mock module avail command use debug in name
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0
test-module/1.0.0-debug
"""

# Expect an error raised
with pytest.raises(ValueError) as exc_info:
check_modulefile('test-module')
exc_info.value.startswith(
"There are multiple modules available for test-module"
)


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_exact_match(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0
test-module/1.0.0-debug
"""

# Test runs without an error
check_modulefile('test-module/1.0.0')


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_exact_match_with_symbolic_version(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0(default)
test-module/1.0.0-debug
"""

# Test runs without an error
check_modulefile('test-module/1.0.0')

# Rerun test with another symbolic version/alias other than default
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0(some_symbolic_name_or_alias)
test-module/1.0.0-debug
"""

# Test runs without an error
check_modulefile('test-module/1.0.0')


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_multiple_modules(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = """/path/to/modulefiles:
test-module/1.0.0
/another/module/path:
test-module/1.0.0
"""

# Expect an error raised
with pytest.raises(ValueError) as exc_info:
check_modulefile('test-module/1.0.0')
exc_info.value.startswith(
"There are multiple modules available for test-module"
)


@patch('payu.envmod.run_module_cmd')
def test_check_modulefile_no_modules_found(mock_module_cmd):
# Mock module avail command
mock_module_cmd.return_value.stderr = ""

# Expect an error raised
with pytest.raises(ValueError) as exc_info:
check_modulefile('test-module/1.0.0')
exc_info.value.startswith("Module is not found: test-module")

0 comments on commit e611bcc

Please sign in to comment.