|
8 | 8 |
|
9 | 9 | from metadata.metadata_table import parse_metadata_file |
10 | 10 | 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 |
12 | 12 | from generator.suite_cap import ( |
13 | 13 | _all_suite_scheme_names, |
14 | 14 | _schemes_with_register, |
@@ -287,6 +287,49 @@ def test_all_phases_have_default_case(self): |
287 | 287 | "phase '{}' missing case default".format(phase)) |
288 | 288 |
|
289 | 289 |
|
| 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 | + |
290 | 333 | class TestWriteSuiteCap(unittest.TestCase): |
291 | 334 |
|
292 | 335 | def test_writes_file(self): |
|
0 commit comments