Skip to content

Commit

Permalink
Feature #2562 version lookup documentation and automated tests (#2773)
Browse files Browse the repository at this point in the history
* set default input version to the latest official release, which is the 2nd highest coordinated release version number in the version lookup table. This assumes that the highest version is what is being developed towards

* per #2562, update Release Guide to include instructions for updating the component versions lookup table in the METplus repository

* Per #2562 and #2597, replace automated test logic that used manage_externals to use component version lookup to get versions of METplotpy/METcalcpy/METdataio

* clean up SonarQube complaints

* turn on some use cases to test changes

* fix clone

* do not get python dependencies for METplotpy/METcalcpy/METdataio installs

* use full path to package to import

* clone METplus Analysis repos on the same level as METplus to match previous location using manage_externals, remove manage_externals config files that are no longer used by the automated tests

* clean up linter complaints

* turn off use cases after confirming that they run successfully

* clean up usage statement formatting

* remove info about updating manage_externals config files used in automated tests that have been removed

* added description of component versions script to Contributor's Guide and added stubs for other utility descriptions

* change format of example results and add text to describe what is being demonstrated from each example

* fix formatting

* fixed bad variable replacement

* Update docs/Contributors_Guide/utilities.rst

---------

Co-authored-by: Julie Prestopnik <[email protected]>
  • Loading branch information
georgemccabe and jprestop authored Nov 7, 2024
1 parent d2d99b4 commit 89c6dc7
Show file tree
Hide file tree
Showing 20 changed files with 417 additions and 148 deletions.
166 changes: 90 additions & 76 deletions .github/jobs/get_use_case_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from metplus.util.string_manip import expand_int_string_to_list
from docker_utils import VERSION_EXT
from metplus import get_metplus_version
from metplus.component_versions import get_component_version

# path to METplus install location in Docker
METPLUS_DOCKER_LOC = '/metplus/METplus'
Expand All @@ -39,7 +40,7 @@
]


def handle_automation_env(host_name, reqs, work_dir):
def handle_automation_env(host_name, reqs):
# if no env is specified, use metplus base environment
conda_env = METPLUS_BASE_ENV

Expand All @@ -60,8 +61,9 @@ def handle_automation_env(host_name, reqs, work_dir):
conda_env_w_ext = f'{conda_env}{VERSION_EXT}'

# start building commands to run before run_metplus.py in Docker
setup_env = []
setup_env.append(_add_to_bashrc('# BELOW WAS ADDED BY TEST SCRIPT'))
setup_env = [
_add_to_bashrc('# BELOW WAS ADDED BY TEST SCRIPT')
]

# add conda bin to beginning of PATH
python_dir = os.path.join('/usr', 'local', 'conda', 'envs',
Expand All @@ -76,35 +78,34 @@ def handle_automation_env(host_name, reqs, work_dir):
else:
py_embed_arg = ''

# get METplus version to determine Externals file to use
# to get METplotpy/METcalcpy/METdataio
# get METplus version to determine which version of
# METplotpy/METcalcpy/METdataio to use
# If stable release, get main branch, otherwise get develop
is_stable_release = len(get_metplus_version().split('-')) == 1
externals_ext = '_stable.cfg' if is_stable_release else '.cfg'
metplus_version = get_metplus_version()

# if any metplotpy/metcalcpy keywords are in requirements list,
# add command to obtain and install METplotpy and METcalcpy
components = []
if any([item for item in PLOTCALC_KEYWORDS if item in str(reqs).lower()]):
ce_file = os.path.join(work_dir, '.github', 'parm',
f'Externals_metplotcalcpy{externals_ext}')
setup_env.extend((
f'cd {METPLUS_DOCKER_LOC}',
f'{work_dir}/manage_externals/checkout_externals -e {ce_file}',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METplotpy',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METcalcpy',
'cd -',
))
components.extend(('METplotpy', 'METcalcpy'))

# if metdataio is in requirements list, add command to obtain METdataio
if 'metdataio' in str(reqs).lower():
ce_file = os.path.join(work_dir, '.github', 'parm',
f'Externals_metdataio{externals_ext}')
components.append('METdataio')

setup_env.append(f'cd {METPLUS_DOCKER_LOC}/..')
for component in components:
version = get_component_version(input_component='METplus',
input_version=metplus_version,
output_component=component,
output_format='main_v{X}.{Y}',
get_dev=False)
setup_env.extend((
f'cd {METPLUS_DOCKER_LOC}',
f'{work_dir}/manage_externals/checkout_externals -e {ce_file}',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METdataio',
'cd -',
'git --version',
f'git clone --single-branch --branch {version} https://github.com/dtcenter/{component}',
f'{python_path} -m pip install --no-deps {METPLUS_DOCKER_LOC}/../{component}',
))
setup_env.append('cd -')

# if metplus is in requirements list,
# add top of METplus repo to PYTHONPATH so metplus can be imported
Expand All @@ -118,7 +119,7 @@ def handle_automation_env(host_name, reqs, work_dir):
setup_env.extend((
f'echo Using environment: dtcenter/metplus-envs:{conda_env_w_ext}',
f'echo cat /usr/local/conda/envs/{conda_env_w_ext}/environments.yml',
f'echo ----------------------------------------',
'echo ----------------------------------------',
f'cat /usr/local/conda/envs/{conda_env_w_ext}/environments.yml',
'echo ----------------------------------------',
))
Expand All @@ -140,6 +141,24 @@ def main(categories, subset_list, work_dir=None,
test_suite = METplusUseCaseSuite()
test_suite.add_use_case_groups(categories, subset_list)

for group_name, use_cases_by_req in test_suite.category_groups.items():
for use_case_by_requirement in use_cases_by_req:
reqs = use_case_by_requirement.requirements

setup_env, py_embed_arg = handle_automation_env(host_name, reqs)

use_case_cmds = _get_use_case_cmds(host_name, use_case_by_requirement, work_dir, group_name, py_embed_arg)

# add commands to set up environment before use case commands
all_commands.append((setup_env, use_case_cmds, reqs))

return all_commands


def _get_use_case_cmds(host_name, use_case_by_requirement, work_dir, group_name, py_embed_arg):
# use status variable to track if any use cases failed
use_case_cmds = []

output_top_dir = os.environ.get('METPLUS_TEST_OUTPUT_BASE', '/data/output')

# use METPLUS_TEST_SETTINGS_CONF if set
Expand All @@ -150,58 +169,53 @@ def main(categories, subset_list, work_dir=None,
'parm',
'test_settings.conf')

for group_name, use_cases_by_req in test_suite.category_groups.items():
for use_case_by_requirement in use_cases_by_req:
reqs = use_case_by_requirement.requirements
if host_name != 'docker':
use_case_cmds.append('status=0')
for use_case in use_case_by_requirement.use_cases:
# add parm/use_cases path to config args if they are conf files
config_args = _get_config_args(use_case.config_args, work_dir, host_name)

output_base = os.path.join(output_top_dir,
group_name.split('-')[0],
use_case.name)
use_case_cmd = (f"run_metplus.py"
f" {' '.join(config_args)}"
f" {py_embed_arg}{test_settings_conf}"
f" config.OUTPUT_BASE={output_base}")
use_case_cmds.append(use_case_cmd)
# check exit code from use case command and
# set status to non-zero value on error
if host_name != 'docker':
use_case_cmds.append("if [ $? != 0 ]; then status=1; fi")

# if any use cases failed, force non-zero exit code with false
if host_name != 'docker':
use_case_cmds.append("if [ $status != 0 ]; then false; fi")

setup_env, py_embed_arg = handle_automation_env(host_name, reqs,
work_dir)

# use status variable to track if any use cases failed
use_case_cmds = []
if host_name != 'docker':
use_case_cmds.append('status=0')
for use_case in use_case_by_requirement.use_cases:
# add parm/use_cases path to config args if they are conf files
config_args = []
ci_overrides = None
for config_arg in use_case.config_args:
if config_arg.endswith('.conf'):
config_arg = os.path.join(work_dir, 'parm',
'use_cases', config_arg)

# look for CI overrides conf file
override_path = os.path.join(config_arg[0:-5],
'ci_overrides.conf')
if os.path.exists(override_path):
ci_overrides = override_path

config_args.append(config_arg)

# add CI overrides config file if running in docker
if ci_overrides and host_name == 'docker':
config_args.append(ci_overrides)

output_base = os.path.join(output_top_dir,
group_name.split('-')[0],
use_case.name)
use_case_cmd = (f"run_metplus.py"
f" {' '.join(config_args)}"
f" {py_embed_arg}{test_settings_conf}"
f" config.OUTPUT_BASE={output_base}")
use_case_cmds.append(use_case_cmd)
# check exit code from use case command and
# set status to non-zero value on error
if host_name != 'docker':
use_case_cmds.append("if [ $? != 0 ]; then status=1; fi")

# if any use cases failed, force non-zero exit code with false
if host_name != 'docker':
use_case_cmds.append("if [ $status != 0 ]; then false; fi")
# add commands to set up environment before use case commands
all_commands.append((setup_env, use_case_cmds, reqs))
return use_case_cmds

return all_commands

def _get_config_args(input_config_args, work_dir, host_name):
config_args = []
ci_overrides = None
for config_arg in input_config_args:
if config_arg.endswith('.conf'):
config_arg = os.path.join(work_dir, 'parm',
'use_cases', config_arg)

# look for CI overrides conf file
override_path = os.path.join(config_arg[0:-5],
'ci_overrides.conf')
if os.path.exists(override_path):
ci_overrides = override_path

config_args.append(config_arg)

# add CI overrides config file if running in docker
if ci_overrides and host_name == 'docker':
config_args.append(ci_overrides)

return config_args


def handle_command_line_args():
Expand Down Expand Up @@ -232,9 +246,9 @@ def handle_command_line_args():


if __name__ == '__main__':
categories, subset_list, _ = handle_command_line_args()
all_commands = main(categories, subset_list)
for setup_commands, use_case_commands, requirements in all_commands:
input_categories, input_subset_list, _ = handle_command_line_args()
commands = main(input_categories, input_subset_list)
for setup_commands, use_case_commands, requirements in commands:
print(f"REQUIREMENTS: {','.join(requirements)}")
if setup_commands:
command_format = ';\\\n'.join(setup_commands.split(';'))
Expand Down
9 changes: 0 additions & 9 deletions .github/parm/Externals_metdataio.cfg

This file was deleted.

9 changes: 0 additions & 9 deletions .github/parm/Externals_metdataio_stable.cfg

This file was deleted.

16 changes: 0 additions & 16 deletions .github/parm/Externals_metplotcalcpy.cfg

This file was deleted.

16 changes: 0 additions & 16 deletions .github/parm/Externals_metplotcalcpy_stable.cfg

This file was deleted.

2 changes: 1 addition & 1 deletion .github/parm/use_case_groups.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
},
{
"category": "medium_range",
"index_list": "3-5,10",
"index_list": "3-5,10",
"run": false
},
{
Expand Down
1 change: 1 addition & 0 deletions docs/Contributors_Guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Contributor's Guide
:numbered:

coding_standards
utilities
basic_components
create_wrapper
conda_env
Expand Down
Loading

0 comments on commit 89c6dc7

Please sign in to comment.