Skip to content

Commit fa3ff46

Browse files
committed
EAMxx: fix doctests and corner-cases in atm_manip
1 parent b5f97f8 commit fa3ff46

File tree

3 files changed

+92
-42
lines changed

3 files changed

+92
-42
lines changed

components/eamxx/cime_config/eamxx_buildnml.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def perform_consistency_checks(case, xml):
126126
>>> case = MockCase({'ATM_NCPL':'24', 'REST_N':2, 'REST_OPTION':'nsteps'})
127127
>>> perform_consistency_checks(case,xml)
128128
Traceback (most recent call last):
129-
CIME.utils.CIMEError: ERROR: rrtmgp::rad_frequency incompatible with restart frequency.
129+
CIME.utils.CIMEError: ERROR: rrtmgp::rad_frequency (3 steps) incompatible with restart frequency (2 steps).
130130
Please, ensure restart happens on a step when rad is ON
131131
>>> case = MockCase({'ATM_NCPL':'24', 'REST_N':10800, 'REST_OPTION':'nseconds'})
132132
>>> perform_consistency_checks(case,xml)
@@ -970,7 +970,7 @@ def create_input_data_list_file(case,caseroot):
970970
permissions = stat.filemode(file_stat.st_mode)
971971

972972
except Exception as e:
973-
raise RuntimeError(f"Error retrieving file info for '{file_path}': {e}")
973+
raise RuntimeError(f"Error retrieving file info for '{file_path}': {e}") from e
974974

975975
curr_user = getpass.getuser()
976976
user_info = pwd.getpwnam(curr_user)

components/eamxx/scripts/atm_manip.py

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ def get_xml_nodes(xml_root, name):
8989
>>> ################ INVALID SYNTAX #######################
9090
>>> get_xml_nodes(tree,'sub::::prop1')
9191
Traceback (most recent call last):
92-
SystemExit: ERROR: Invalid xml node name format, 'sub::::prop1' contains ::::
92+
SystemExit: ERROR: Invalid xml node name format, 'sub::::prop1' contains '::::'
9393
>>> ################ VALID USAGE #######################
9494
>>> get_xml_nodes(tree,'invalid::prop1')
9595
[]
96-
>>> [item.text for item in get_xml_nodes(tree,'prop1')]
96+
>>> [item.text for item in get_xml_nodes(tree,'ANY::prop1')]
9797
['one', 'two']
9898
>>> [item.text for item in get_xml_nodes(tree,'::prop1')]
9999
['one']
@@ -102,10 +102,10 @@ def get_xml_nodes(xml_root, name):
102102
>>> item = get_xml_nodes(tree,'prop2')[0]
103103
>>> parent_map = create_parent_map(tree)
104104
>>> [p.tag for p in get_parents(item, parent_map)]
105-
['root', 'sub']
105+
['sub']
106106
"""
107107
expect('::::' not in name,
108-
"Badly formatted node name: found '::::'")
108+
f"Invalid xml node name format, '{name}' contains '::::'")
109109

110110
tokens = name.split("::")
111111
expect (tokens[-1] != '', "Input query string ends with '::'. It should end with an actual node name")
@@ -156,7 +156,8 @@ def get_xml_nodes(xml_root, name):
156156
error_str = f"{name} is ambiguous. Use ANY in the node path to allow multiple matches. Matches:\n"
157157
for node in result:
158158
parents = get_parents(node, parent_map)
159-
name = "::".join(e.tag for e in parents) + "::" + node.tag
159+
name = "::".join(e.tag for e in parents)
160+
name = node.tag if name=="" else name + "::" + node.tag
160161
error_str += " " + name + "\n"
161162

162163
expect(False, error_str)
@@ -393,8 +394,8 @@ def atm_config_chg_impl(xml_root, change):
393394
>>> atm_config_chg_impl(tree,'prop1=three')
394395
Traceback (most recent call last):
395396
SystemExit: ERROR: prop1 is ambiguous. Use ANY in the node path to allow multiple matches. Matches:
396-
root::prop1
397-
root::sub::prop1
397+
prop1
398+
sub::prop1
398399
<BLANKLINE>
399400
>>> ################ VALID USAGE #######################
400401
>>> atm_config_chg_impl(tree,'::prop1=two')
@@ -405,7 +406,7 @@ def atm_config_chg_impl(xml_root, change):
405406
True
406407
>>> atm_config_chg_impl(tree,'ANY::prop1=three')
407408
True
408-
>>> [item.text for item in get_xml_nodes(tree,'prop1')]
409+
>>> [item.text for item in get_xml_nodes(tree,'ANY::prop1')]
409410
['three', 'three']
410411
>>> ################ TEST APPEND += #################
411412
>>> atm_config_chg_impl(tree,'a+=4')
@@ -465,14 +466,49 @@ def get_parents(elem, parent_map):
465466
be the furthest ancestor, last item will be direct parent)
466467
"""
467468
results = []
468-
if elem in parent_map and parent_map[elem] is not None:
469+
if not is_root(elem,parent_map):
469470
parent = parent_map[elem]
470471
results = get_parents(parent, parent_map)
471-
if parent_map[parent] is not None:
472+
if not is_root(parent,parent_map):
472473
results.append(parent)
473474

474475
return results
475476

477+
###############################################################################
478+
def is_anchestor_of(parent,child,parent_map):
479+
###############################################################################
480+
"""
481+
>>> xml = '''
482+
... <root>
483+
... <prop1>one</prop1>
484+
... <sub>
485+
... <prop1>two</prop1>
486+
... <prop2 type="integer" valid_values="1,2">2</prop2>
487+
... </sub>
488+
... </root>
489+
... '''
490+
>>> import xml.etree.ElementTree as ET
491+
>>> tree = ET.fromstring(xml)
492+
>>> parent_map = create_parent_map(tree)
493+
>>> sub = get_xml_nodes(tree,'sub')[0]
494+
>>> sub_prop1 = get_xml_nodes(tree,'sub::prop1')[0]
495+
>>> is_anchestor_of(sub,sub_prop1,parent_map)
496+
True
497+
>>> is_anchestor_of(sub_prop1,sub,parent_map)
498+
False
499+
"""
500+
curr = child
501+
while curr is not None:
502+
if curr is parent:
503+
return True
504+
curr = parent_map[curr]
505+
return False
506+
507+
###############################################################################
508+
def is_root (node,parent_map):
509+
###############################################################################
510+
return parent_map[node] is None
511+
476512
###############################################################################
477513
def print_var_impl(node,parent_map,full,dtype,value,valid_values,print_style="invalid",indent=""):
478514
###############################################################################
@@ -482,21 +518,23 @@ def print_var_impl(node,parent_map,full,dtype,value,valid_values,print_style="in
482518
# This is not a leaf, so print all nested nodes.
483519
for child in node:
484520
# Since prints are nicely nested, use 'node-name' as print style
485-
print_var_impl(child,{},full,dtype,value,valid_values,'node-name',indent+" ")
521+
print_var_impl(child,{},full,dtype,value,valid_values,'node-name',indent+" ")
486522
return
487523

488-
# print (f"printing leaf={node.tag}, len(parent_map)={len(parent_map)}")
489524
expect (print_style in ["node-name","full-scope","parent-scope"],
490525
f"Invalid print_style '{print_style}' for print_var_impl. Use 'full' or 'short'.")
491526

492527
if print_style=="node-name":
493528
# Just the inner most name
494529
name = node.tag
495530
elif print_style=="parent-scope":
496-
name = parent_map[node].tag + "::" + node.tag
531+
parent = parent_map[node]
532+
name = node.tag if is_root(parent,parent_map) else parent.tag + "::" + node.tag
497533
else:
498534
parents = get_parents(node, parent_map)
499-
name = "::".join(e.tag for e in parents) + "::" + node.tag
535+
name = "::".join(e.tag for e in parents)
536+
name += "::" if parents else ""
537+
name += node.tag
500538

501539
if full:
502540
expect ("type" in node.attrib.keys(),
@@ -563,7 +601,19 @@ def print_var(xml_root,parent_map,var,full,dtype,value,valid_values,print_style=
563601
# Get matches
564602
matches = get_xml_nodes(xml_root,var)
565603

566-
for node in matches:
604+
# If ANY is in the var name, we may hit the case where one of the matches
605+
# is a parent of another match. In this case, we want to get rid of
606+
unique_matches = []
607+
for i in matches:
608+
add_this = True
609+
for j in matches:
610+
if i is not j and is_anchestor_of(j,i,parent_map):
611+
add_this = False
612+
613+
if add_this:
614+
unique_matches.append(i)
615+
616+
for node in unique_matches:
567617
print_var_impl(node,parent_map,full,dtype,value,valid_values,print_style,indent)
568618

569619
###############################################################################
@@ -584,23 +634,22 @@ def atm_query_impl(xml_root,variables,listall=False,full=False,value=False,
584634
>>> tree = ET.fromstring(xml)
585635
>>> vars = ['prop2','::prop1']
586636
>>> success = atm_query_impl(tree, vars)
587-
root::sub::prop2: 2
588-
root::prop1: one
637+
sub::prop2: 2
638+
prop1: one
589639
>>> success = atm_query_impl(tree, [], listall=True, valid_values=True)
590-
root
640+
prop1: <valid values not provided>
641+
sub:
591642
prop1: <valid values not provided>
592-
sub
593-
prop1: <valid values not provided>
594-
prop2: ['1', '2']
643+
prop2: ['1', '2']
595644
>>> success = atm_query_impl(tree,['prop1'], grep=True)
596-
root::prop1: one
645+
prop1: one
597646
sub::prop1: two
598647
"""
599648

600649
if not parent_map:
601650
parent_map = create_parent_map(xml_root)
602651
if listall:
603-
print_var(xml_root,parent_map,'ANY',full,dtype,value,valid_values,"node-name"," ")
652+
print_var(xml_root,parent_map,'ANY',full,dtype,value,valid_values,"node-name"," ")
604653

605654
elif grep:
606655
for regex in variables:
@@ -610,7 +659,7 @@ def atm_query_impl(xml_root,variables,listall=False,full=False,value=False,
610659
if len(xml_root)>0:
611660
parents = get_parents(xml_root,parent_map)
612661
print (f"{'::'.join([p.tag for p in parents]) + '::' + xml_root.tag}:")
613-
print_var(xml_root,parent_map,'ANY',full,dtype,value,valid_values,"node-name"," ")
662+
print_var(xml_root,parent_map,'ANY',full,dtype,value,valid_values,"node-name"," ")
614663
else:
615664
for elem in xml_root:
616665
if len(elem)>0:
@@ -624,6 +673,6 @@ def atm_query_impl(xml_root,variables,listall=False,full=False,value=False,
624673
else:
625674
for var in variables:
626675
pmap = {} if var=='ANY' else parent_map
627-
print_var(xml_root,pmap,var,full,dtype,value,valid_values,"parent-scope"," ")
676+
print_var(xml_root,pmap,var,full,dtype,value,valid_values,"parent-scope"," ")
628677

629678
return True

components/eamxx/scripts/cime-nml-tests

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class TestBuildnml(unittest.TestCase):
119119
def setUp(self):
120120
###########################################################################
121121
self._dirs_to_cleanup = []
122+
self._common_case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
122123

123124
###########################################################################
124125
def tearDown(self):
@@ -174,7 +175,7 @@ class TestBuildnml(unittest.TestCase):
174175
"""
175176
Test that atmchanges are not lost when eamxx setup is called
176177
"""
177-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
178+
case = self._common_case
178179

179180
self._chg_atmconfig([("atm_log_level", "trace"), ("output_to_screen", "true")], case)
180181

@@ -192,7 +193,7 @@ class TestBuildnml(unittest.TestCase):
192193
"""
193194
Test that the append attribute for array-type params in namelist defaults works as expected
194195
"""
195-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
196+
case = self._common_case
196197

197198
# Add testOnly proc
198199
self._chg_atmconfig([("mac_aero_mic::atm_procs_list", "shoc,cldFraction,spa,p3,testOnly")], case)
@@ -213,7 +214,7 @@ class TestBuildnml(unittest.TestCase):
213214
"""
214215
Test that var+=value syntax behaves as expected
215216
"""
216-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
217+
case = self._common_case
217218

218219
# Append to an existing entry
219220
name = 'output_yaml_files'
@@ -230,7 +231,7 @@ class TestBuildnml(unittest.TestCase):
230231
"""
231232
Test that atmchanges are lost when resetting
232233
"""
233-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
234+
case = self._common_case
234235

235236
self._chg_atmconfig([("atm_log_level", "trace")], case, reset=True)
236237

@@ -240,7 +241,7 @@ class TestBuildnml(unittest.TestCase):
240241
"""
241242
Test that atmchanges are lost if SCREAM_HACK_XML=TRUE
242243
"""
243-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
244+
case = self._common_case
244245

245246
run_cmd_assert_result(self, "./xmlchange SCREAM_HACK_XML=TRUE", from_dir=case)
246247

@@ -267,7 +268,7 @@ class TestBuildnml(unittest.TestCase):
267268
"""
268269
Test that atmchange works when using 'namespace' syntax foo::bar
269270
"""
270-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
271+
case = self._common_case
271272

272273
self._chg_atmconfig([("p3::enable_precondition_checks", "false")], case)
273274

@@ -277,7 +278,7 @@ class TestBuildnml(unittest.TestCase):
277278
"""
278279
Test that atmchange works for array data
279280
"""
280-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
281+
case = self._common_case
281282

282283
self._chg_atmconfig([("surf_mom_flux", "40.0,2.0")], case)
283284

@@ -287,7 +288,7 @@ class TestBuildnml(unittest.TestCase):
287288
"""
288289
Test that atmchange works with ANY
289290
"""
290-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
291+
case = self._common_case
291292

292293
self._chg_atmconfig([("ANY::enable_precondition_checks", "false")], case)
293294

@@ -297,7 +298,7 @@ class TestBuildnml(unittest.TestCase):
297298
"""
298299
Test atmchange with ANY followed by an atmchange of one of them
299300
"""
300-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
301+
case = self._common_case
301302

302303
self._chg_atmconfig([("ANY::enable_precondition_checks", "false"),
303304
("p3::enable_precondition_checks", "true")], case)
@@ -308,7 +309,7 @@ class TestBuildnml(unittest.TestCase):
308309
"""
309310
Test atmchanges that add atm procs
310311
"""
311-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
312+
case = self._common_case
312313

313314
self._chg_atmconfig([("mac_aero_mic::atm_procs_list", "shoc,cldFraction,spa,p3,testOnly")], case)
314315

@@ -322,7 +323,7 @@ class TestBuildnml(unittest.TestCase):
322323
"""
323324
Test atmchanges that add atm procs
324325
"""
325-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
326+
case = self._common_case
326327

327328
# modifying a procs list requires known processes or "_" pre/post suffixes
328329
stat, out, err = run_cmd ("./atmchange mac_aero_mic::atm_procs_list=shoc,cldfraction,spa,p3,spiderman",
@@ -336,7 +337,7 @@ class TestBuildnml(unittest.TestCase):
336337
"""
337338
Test atmchange does not change buffer if syntax was wrong
338339
"""
339-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
340+
case = self._common_case
340341

341342
# Attempting a bad change should not alter the content of SCREAM_ATMCHANGE_BUFFER
342343
old = run_cmd_no_fail ("./xmlquery --value SCREAM_ATMCHANGE_BUFFER",from_dir=case)
@@ -353,7 +354,7 @@ class TestBuildnml(unittest.TestCase):
353354
"""
354355
Test atmchange errors out with invalid param names
355356
"""
356-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
357+
case = self._common_case
357358

358359
stat, out, err = run_cmd ("./atmchange p3::non_existent=3",from_dir=case)
359360
expect (stat!=0,"Command './atmchange p3::non_existent=3' should have failed")
@@ -364,7 +365,7 @@ class TestBuildnml(unittest.TestCase):
364365
"""
365366
Test atmchanges that add atm proc groups
366367
"""
367-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
368+
case = self._common_case
368369

369370
out = run_cmd_no_fail ("./atmchange mac_aero_mic::atm_procs_list=shoc,_my_group_",from_dir=case)
370371

@@ -380,7 +381,7 @@ class TestBuildnml(unittest.TestCase):
380381
"""
381382
Test atmchanges that remove atm procs
382383
"""
383-
case = self._create_test("SMS.ne30_ne30.F2010-SCREAMv1 --no-build")
384+
case = self._common_case
384385

385386
self._chg_atmconfig([("mac_aero_mic::atm_procs_list", "shoc,cldFraction,spa")], case)
386387

0 commit comments

Comments
 (0)