-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Co-authored-by: George McCabe <[email protected]>
- Loading branch information
1 parent
0fd5fca
commit 9e350b1
Showing
1 changed file
with
247 additions
and
0 deletions.
There are no files selected for viewing
247 changes: 247 additions & 0 deletions
247
internal/tests/pytests/wrappers/gfdl_tracker/test_gfdl_tracker.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
import os | ||
import pytest | ||
from unittest import mock | ||
from metplus.wrappers import gfdl_tracker_wrapper as gf | ||
|
||
|
||
time_fmt = "%Y%m%d%H" | ||
run_times = ["2023080700", "2023080712", "2023080800"] | ||
|
||
# Helper class for string matching | ||
class MatchSubstring(str): | ||
def __eq__(self, other): | ||
return self in other | ||
|
||
def set_minimum_config_settings(config): | ||
# set config variables to prevent command from running and bypass check | ||
# if input files actually exist | ||
config.set("config", "DO_NOT_RUN_EXE", True) | ||
config.set("config", "INPUT_MUST_EXIST", False) | ||
|
||
# set process and time config variables | ||
config.set("config", "PROCESS_LIST", "GFDLTracker") | ||
config.set("config", "LOOP_BY", "INIT") | ||
config.set("config", "INIT_TIME_FMT", time_fmt) | ||
config.set("config", "INIT_BEG", run_times[0]) | ||
config.set("config", "INIT_END", run_times[-1]) | ||
config.set("config", "INIT_INCREMENT", "12H") | ||
config.set("config", "LEAD_SEQ", "12H") | ||
config.set("config", "LOOP_ORDER", "processes") | ||
config.set('config', "GFDL_TRACKER_RUNTIME_FREQ","RUN_ONCE_PER_INIT_OR_VALID") | ||
config.set("config", "GFDL_TRACKER_EXEC", "fake_exe") | ||
config.set("config", "GFDL_TRACKER_GRIB_VERSION", 1) | ||
config.set("config", "GFDL_TRACKER_SKIP_TIMES", "") | ||
config.set("config", "GFDL_TRACKER_MANDATORY", True) | ||
config.set("config", "GFDL_TRACKER_SKIP_IF_OUTPUT_EXISTS", False) | ||
|
||
config.set( | ||
"config", | ||
"GFDL_TRACKER_NML_TEMPLATE_FILE", | ||
"nml_template_file{init?fmt=%Y%m%d%H}.nml", | ||
), | ||
config.set( | ||
"config", "GFDL_TRACKER_INPUT_TEMPLATE", "input_file{init?fmt=%Y%m%d%H}.txt" | ||
), | ||
config.set( | ||
"config", | ||
"GFDL_TRACKER_TC_VITALS_INPUT_TEMPLATE", | ||
"tc_vitals_template_file{init?fmt=%Y%m%d%H}", | ||
), | ||
config.set( | ||
"config", | ||
"GFDL_TRACKER_OUTPUT_TEMPLATE", | ||
"out_template_file{init?fmt=%Y%m%d%H}.nml", | ||
), | ||
config.set("config", "GFDL_TRACKER_OUTPUT_DIR", "outdir/") | ||
config.set("config", "GFDL_TRACKER_SGV_TEMPLATE_FILE", "sgv_template") | ||
|
||
|
||
@pytest.mark.wrapper_b | ||
def test_gfdl_tracker_basic(metplus_config, monkeypatch): | ||
config = metplus_config | ||
set_minimum_config_settings(config) | ||
|
||
# GFDLTrackerWrapper does a lot of file manipulation. The approach | ||
# here is to ignore all those operations and just create and run a | ||
# basic wrapper object. A more complete test would create all the | ||
# input files and then check files are created/removed as expected. | ||
with mock.patch.object(gf.os, "symlink", return_value=mock.MagicMock): | ||
with mock.patch.object(gf.os.path, "exists"): | ||
with mock.patch.object(gf.shutil, "copyfile"): | ||
with mock.patch.object(gf.os, "remove"): | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.create_fort_14_file = mock.MagicMock | ||
wrapper.create_fort_15_file = mock.MagicMock | ||
wrapper.sub_template = mock.MagicMock | ||
all_cmds = wrapper.run_all_times() | ||
assert wrapper.isOK | ||
assert len(all_cmds) == 6 | ||
|
||
# Check some config items are set correctly | ||
expected_values = { | ||
"INPUT_GRIB_VERSION": 1, | ||
"INDEX_APP": "fake_exe/grbindex.exe", | ||
"TRACKER_APP": "fake_exe/gettrk.exe", | ||
"INPUT_TEMPLATE": "input_file{init?fmt=%Y%m%d%H}.txt", | ||
"TC_VITALS_INPUT_TEMPLATE": "tc_vitals_template_file{init?fmt=%Y%m%d%H}", | ||
"NML_TEMPLATE_FILE": "nml_template_file{init?fmt=%Y%m%d%H}.nml", | ||
"KEEP_INTERMEDIATE": False, | ||
} | ||
|
||
for key, expected_value in expected_values.items(): | ||
assert expected_value == wrapper.c_dict[key] | ||
|
||
|
||
@pytest.mark.wrapper_b | ||
def test_conf_error_log(metplus_config): | ||
'''Check correct error messages are logged''' | ||
config = metplus_config | ||
|
||
with pytest.raises( | ||
ValueError, match="GFDL_TRACKER_EXEC cannot be set to or contain '/path/to'" | ||
): | ||
gf.GFDLTrackerWrapper(config) | ||
|
||
config.set("config", "GFDL_TRACKER_EXEC", "fake_exe") | ||
with mock.patch.object(gf.os.path, "exists"): | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.logger.error.assert_called_once_with( | ||
MatchSubstring("GFDL_TRACKER_GRIB_VERSION () must be 1 or 2") | ||
) | ||
|
||
config.set("config", "GFDL_TRACKER_GRIB_VERSION", 2) | ||
with mock.patch.object(gf.os.path, "exists"): | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
|
||
expected_errs = [ | ||
"Must set GFDL_TRACKER_NML_TEMPLATE_FILE", | ||
"GFDL_TRACKER_INPUT_TEMPLATE must be set", | ||
"GFDL_TRACKER_TC_VITALS_INPUT_TEMPLATE must be set", | ||
"GFDL_TRACKER_OUTPUT_TEMPLATE must be set", | ||
"GFDL_TRACKER_OUTPUT_DIR must be set", | ||
] | ||
|
||
for msg in expected_errs: | ||
wrapper.logger.error.assert_any_call(MatchSubstring(msg)) | ||
|
||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.logger.error.assert_any_call( | ||
MatchSubstring("GRIB index exe does not exist: ") | ||
) | ||
|
||
config.set("config", "TRACKER_APP", __file__) | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.logger.error.assert_any_call( | ||
MatchSubstring("Must set GFDL_TRACKER_NML_TEMPLATE_FILE") | ||
) | ||
|
||
config.set("config", "GFDL_TRACKER_NML_TEMPLATE_FILE", "gfdl_nml_template_file") | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.logger.error.assert_any_call( | ||
MatchSubstring("GFDL_TRACKER_NML_TEMPLATE_FILE does not exist: ") | ||
) | ||
|
||
|
||
@pytest.mark.wrapper_b | ||
def test_handle_gen_vitals(tmp_path_factory, metplus_config): | ||
tmp_dir = tmp_path_factory.mktemp('gfdl') | ||
file_name = 'input_tmplate' | ||
open(os.path.join(tmp_dir,file_name), 'a').close() | ||
|
||
config = metplus_config | ||
|
||
set_minimum_config_settings(config) | ||
config.set('config','GFDL_TRACKER_GEN_VITALS_INPUT_TEMPLATE', file_name) | ||
config.set('config','GFDL_TRACKER_OUTPUT_DIR', tmp_dir) | ||
config.set('config','GFDL_TRACKER_GEN_VITALS_INPUT_DIR', tmp_dir) | ||
|
||
wrapper = gf.GFDLTrackerWrapper(config) | ||
|
||
result = wrapper.handle_gen_vitals({}) | ||
assert result == True | ||
assert os.path.exists(os.path.join(tmp_dir, 'fort.67')) | ||
assert os.path.exists(os.path.join(tmp_dir, 'tcvit_genesis_storms.txt')) | ||
|
||
result = wrapper.handle_gen_vitals({}) | ||
assert result == True | ||
wrapper.logger.debug.assert_any_call( | ||
MatchSubstring('Gen vitals file already exists: ') | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'file_exists,run_type', | ||
[ | ||
(False,'foo'), | ||
(True,'midlat'), | ||
], | ||
) | ||
@pytest.mark.wrapper_b | ||
def test_create_fort_14_file(tmp_path_factory, metplus_config, file_exists, run_type): | ||
tmp_dir = tmp_path_factory.mktemp('fort14') | ||
tc_vitals = os.path.join(tmp_dir,'tc_vitals') | ||
full_path = os.path.join(tmp_dir,'fort.14') | ||
if file_exists: | ||
open(full_path, 'w').close() | ||
|
||
config = metplus_config | ||
set_minimum_config_settings(config) | ||
config.set('config','GFDL_TRACKER_OUTPUT_DIR', tmp_dir) | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
wrapper.c_dict['REPLACE_CONF_TRACKERINFO_TYPE'] = run_type | ||
|
||
actual = wrapper.create_fort_14_file(tc_vitals) | ||
|
||
if file_exists: | ||
assert os.path.islink(full_path) | ||
else: | ||
assert os.path.exists(full_path) | ||
assert actual == None | ||
|
||
|
||
@pytest.mark.wrapper_b | ||
def test_create_fort_15_file(tmp_path_factory, metplus_config): | ||
tmp_dir = tmp_path_factory.mktemp('fort15') | ||
full_path = os.path.join(tmp_dir,'fort.15') | ||
lead_mins = [5,555,55555] | ||
expected = '\n'.join([' 1 5',' 2 555',' 3 55555']) | ||
|
||
|
||
config = metplus_config | ||
set_minimum_config_settings(config) | ||
config.set('config','GFDL_TRACKER_OUTPUT_DIR', tmp_dir) | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
|
||
wrapper.create_fort_15_file(lead_mins) | ||
assert os.path.exists(full_path) | ||
with open(full_path, 'r') as f: | ||
actual = f.read() | ||
|
||
assert actual == expected | ||
|
||
|
||
@pytest.mark.wrapper_b | ||
def test_sub_template(tmp_path_factory, metplus_config): | ||
tmp_dir = tmp_path_factory.mktemp('sub_template') | ||
out_path = os.path.join(tmp_dir,'outfile.txt') | ||
template_file = os.path.join(tmp_dir,'template.txt') | ||
|
||
sub_dict = {'feature1': 'tornado', 'feature2': 'hurricane'} | ||
content = 'Sometimes we call a twister a ${feature1}, and a cyclone a ${feature2}!' | ||
expected = 'Sometimes we call a twister a tornado, and a cyclone a hurricane!\n' | ||
|
||
with open(template_file, 'w') as tp: | ||
tp.write(content) | ||
|
||
config = metplus_config | ||
set_minimum_config_settings(config) | ||
wrapper = gf.GFDLTrackerWrapper(config) | ||
|
||
wrapper.sub_template(template_file, out_path, sub_dict) | ||
|
||
assert os.path.exists(out_path) | ||
with open(out_path, 'r') as f: | ||
actual = f.read() | ||
|
||
assert expected == actual | ||
|