Skip to content

Commit f0bf618

Browse files
committed
Bug fix in capgen-ng: if all groups are called, check for errflg /= 1 in between
1 parent a1d9a00 commit f0bf618

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

capgen-ng/generator/suite_cap.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,15 @@ def _emit_group_call(resolved_group, indent):
10661066
lines.append('{} {}{}'.format(indent, lname, sep))
10671067
else:
10681068
lines.append('{}call {}()'.format(indent, cap_sub))
1069+
# Stop and propagate on the first group error. Each group phase
1070+
# subroutine resets ``errflg = 0`` on entry, so without this guard a
1071+
# ``group_name='all'`` dispatch would let a LATER group's success
1072+
# overwrite an EARLIER group's failure -- the error (and its message)
1073+
# would be silently masked and only resurface downstream as an
1074+
# "invalid group state" when ``run`` finds the failed group never
1075+
# reached ``IN_TIMESTEP``. Mirrors the per-scheme call guards.
1076+
if errflg_local:
1077+
lines.append('{}if ({} /= 0) return'.format(indent, errflg_local))
10691078

10701079
if has_group_name:
10711080
grp_local = group_name_entry.local_name

unit-tests/test_suite_cap.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from metadata.metadata_table import parse_metadata_file
1010
from metadata.variable_resolver import build_flat_host_dict, SchemeStore
11-
from generator.suite_resolver import resolve_suite
11+
from generator.suite_resolver import resolve_suite, ResolvedGroup
1212
from generator.suite_cap import (
1313
_all_suite_scheme_names,
1414
_schemes_with_register,
@@ -287,6 +287,49 @@ def test_all_phases_have_default_case(self):
287287
"phase '{}' missing case default".format(phase))
288288

289289

290+
class TestGroupDispatchErrorPropagation(unittest.TestCase):
291+
"""A ``group_name='all'`` dispatch must stop and return on the FIRST
292+
group's error. Each group phase subroutine resets ``errflg=0`` on entry,
293+
so without a guard between group calls a later group's success would mask
294+
an earlier group's failure -- which then resurfaces downstream only as an
295+
"invalid group state" when ``run`` finds the failed group never reached
296+
``IN_TIMESTEP``. (Regression: CAM-SIMA cam4 physics_before_coupler.)"""
297+
298+
def _two_group_run_all_block(self):
299+
sr, store = _resolve()
300+
g0 = sr.groups[0]
301+
# Synthesize a second group that shares the first's phase calls so the
302+
# case('', 'all') path emits two group calls.
303+
sr.groups.append(ResolvedGroup(
304+
group_name='physics_second',
305+
phase_calls=g0.phase_calls,
306+
dim_uses=g0.dim_uses,
307+
))
308+
text = '\n'.join(
309+
_generate_suite_cap('test_simple', sr, store, _load_full_host_dict())
310+
)
311+
sub = 'subroutine test_simple_physics_run'
312+
s = text.index(sub)
313+
e = text.index('end ' + sub, s)
314+
block = text[s:e]
315+
a = block.index("case('', 'all')")
316+
nxt = block.index("case('physics", a + 1) # first individual group case
317+
return block[a:nxt]
318+
319+
def test_guard_between_group_calls(self):
320+
all_block = self._two_group_run_all_block()
321+
# Each of the two group calls is followed by an errflg guard.
322+
self.assertEqual(
323+
all_block.count('if (errflg /= 0) return'), 2, all_block
324+
)
325+
# The guard after the first call must precede the second call so the
326+
# second group is unreachable once the first has failed.
327+
first_call = all_block.index('call physics_run(')
328+
guard = all_block.index('if (errflg /= 0) return', first_call)
329+
second_call = all_block.index('call physics_second_run(')
330+
self.assertLess(guard, second_call, all_block)
331+
332+
290333
class TestWriteSuiteCap(unittest.TestCase):
291334

292335
def test_writes_file(self):

0 commit comments

Comments
 (0)