clm5/python/ctsm/test/test_unit_utils.py
2024-05-09 15:14:01 +08:00

334 lines
9.9 KiB
Python

#!/usr/bin/env python3
"""Unit tests for utils and config_utils
"""
import tempfile
import shutil
import unittest
import os
from ctsm import unit_testing
from ctsm.utils import fill_template_file
from ctsm.config_utils import lon_range_0_to_360, _handle_config_value
# Allow names that pylint doesn't like, because otherwise I find it hard
# to make readable unit test names
# pylint: disable=invalid-name
class TestUtilsFillTemplateFile(unittest.TestCase):
"""Tests of utils: fill_template_file"""
def setUp(self):
self._previous_dir = os.getcwd()
self._testdir = tempfile.mkdtemp()
def tearDown(self):
os.chdir(self._previous_dir)
shutil.rmtree(self._testdir, ignore_errors=True)
def test_fillTemplateFile_basic(self):
"""Basic test of fill_template_file"""
template_path = os.path.join(self._testdir, "template.txt")
final_path = os.path.join(self._testdir, "final.txt")
template_contents = """\
Hello
$foo
Goodbye
$bar
"""
with open(template_path, "w") as f:
f.write(template_contents)
fillins = {"foo": "aardvark", "bar": "zyzzyva"}
fill_template_file(template_path, final_path, fillins)
expected_final_text = """\
Hello
aardvark
Goodbye
zyzzyva
"""
with open(final_path) as f:
final_contents = f.read()
self.assertEqual(final_contents, expected_final_text)
class TestUtilsLonRange0to360(unittest.TestCase):
"""Test of utils: lon_range_0_to_360"""
def test_lonRange0To360_lonIsNeg180(self):
"""
Tests that negative inputs to lon_range_0_to_360 get 360 added to them
"""
inval = -180
result = lon_range_0_to_360(inval)
self.assertEqual(result, inval + 360)
def test_lonRange0To360_lonIsNegGreaterThan1(self):
"""
Tests that negative inputs to lon_range_0_to_360 get 360 added to them
"""
inval = -0.001
result = lon_range_0_to_360(inval)
self.assertEqual(result, inval + 360)
def test_lonRange0To360_lonIs0(self):
"""
Tests that input to lon_range_0_to_360 of 0 remains unchanged
"""
inval = 0
result = lon_range_0_to_360(inval)
self.assertEqual(result, inval)
def test_lonRange0To360_lonIs360(self):
"""
Tests that input to lon_range_0_to_360 of 360 remains unchanged
"""
inval = 360
result = lon_range_0_to_360(inval)
self.assertEqual(result, inval)
def test_lonRange0To360_outOfBounds(self):
"""
Tests that lon_range_0_to_360 aborts gracefully when lon = 361
"""
with self.assertRaisesRegex(SystemExit, "lon_in needs to be in the range 0 to 360"):
_ = lon_range_0_to_360(361)
class TestUtilsHandleConfigValue(unittest.TestCase):
"""Test of utils: _handle_config_value"""
def test_handleConfigValue_UnsetCantBeUnset(self):
"""
Tests the handling of UNSET variable read in from a .cfg file
for which can_be_unset = False
"""
val = "UNSET"
item = "varname_in_cfg_file"
default = None
is_list = False
convert_to_type = None
can_be_unset = False
allowed_values = None
errmsg = "Must set a value for .cfg file variable: {}".format(item)
with self.assertRaisesRegex(SystemExit, errmsg):
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
def test_handleConfigValue_UnsetCanBeUnset(self):
"""
Tests the handling of UNSET variable read in from a .cfg file
for which can_be_unset = True
"""
val = "UNSET"
item = "varname_in_cfg_file"
default = [True, False, True]
is_list = True
convert_to_type = None
can_be_unset = True
allowed_values = None
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
self.assertEqual(val, default)
def test_handleConfigValue_convertToBoolFail(self):
"""
Tests the handling of misspelled boolean read in from a .cfg file
Also test whether the code can read a list of booleans
"""
val = "False Tree False" # intentionally misspelled True
item = "varname_in_cfg_file"
default = None
is_list = True
convert_to_type = bool
can_be_unset = False
allowed_values = None
errmsg = "Non-boolean value found for .cfg file variable: {}".format(item)
with self.assertRaisesRegex(SystemExit, errmsg):
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
def test_handleConfigValue_convertToBoolPass(self):
"""
Tests the handling of boolean read in from a .cfg file
Also test whether the code can read a list of booleans
"""
val = "yes no"
item = "varname_in_cfg_file"
default = None
is_list = True
convert_to_type = bool
can_be_unset = False
allowed_values = None
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
self.assertTrue(val[0])
self.assertFalse(val[1])
def test_handleConfigValue_convertToTypePass(self):
"""
Tests the handling of non-boolean list from a .cfg file
"""
val = "-9 0.001"
item = "varname_in_cfg_file"
default = None
is_list = True
convert_to_type = float
can_be_unset = False
allowed_values = None
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
self.assertEqual(val[0], -9)
self.assertEqual(val[1], 0.001)
def test_handleConfigValue_convertToTypeFail(self):
"""
Tests the handling of an incorrectly entered list from a .cfg file
"""
val = "1 2 3 x 5 6 7"
item = "varname_in_cfg_file"
default = None
is_list = True
convert_to_type = float
can_be_unset = False
allowed_values = None
errmsg = "Wrong type for .cfg file variable: {}".format(item)
with self.assertRaisesRegex(SystemExit, errmsg):
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
def test_handleConfigValue_allowedValsFail(self):
"""
Tests that the code aborts if val does not include all allowed_values
"""
val = "1 2 3 4.5 6 7"
item = "varname_in_cfg_file"
default = None
is_list = True
convert_to_type = float
can_be_unset = False
allowed_values = [1, 2, 3, 4, 5, 6, 7]
v = 4.5 # v must equal the misstyped value in val
errmsg = "{} is not an allowed value for {} in .cfg file. Check allowed_values".format(
v, item
)
with self.assertRaisesRegex(SystemExit, errmsg):
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
def test_handleConfigValue_isListFail(self):
"""
Tests that the code aborts if we forget to set is_list = True
"""
val = "True False"
item = "varname_in_cfg_file"
default = None
is_list = False
convert_to_type = bool
can_be_unset = False
allowed_values = None
errmsg = "More than 1 element found for .cfg file variable: {}".format(item)
with self.assertRaisesRegex(SystemExit, errmsg):
val = _handle_config_value(
var=val,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
def test_handleConfigValue_isListFalse(self):
"""
Tests that the code works for a basic case of is_list = False
"""
val_in = "0.5"
item = "varname_in_cfg_file"
default = None
is_list = False
convert_to_type = float
can_be_unset = False
allowed_values = None
val_out = _handle_config_value(
var=val_in,
default=default,
item=item,
is_list=is_list,
convert_to_type=convert_to_type,
can_be_unset=can_be_unset,
allowed_values=allowed_values,
)
self.assertEqual(val_out, float(val_in))
if __name__ == "__main__":
unit_testing.setup_for_tests()
unittest.main()