@@ -122,6 +122,23 @@ def test_jit_invalid_type_raises(target):
122122 Runner (target , monitors = None , jit = 'not-a-jit' , progress_bar = False )
123123
124124
125+ def test_jit_dict_does_not_mutate_caller (target ):
126+ # P15-H1: the caller's jit dict must not be mutated (no key popped).
127+ d = {'train' : False , C .PREDICT_PHASE : False }
128+ Runner (target , monitors = None , jit = d , progress_bar = False )
129+ assert d == {'train' : False , C .PREDICT_PHASE : False }
130+ assert C .PREDICT_PHASE in d
131+
132+
133+ def test_jit_dict_origin_jit_preserves_predict_key (target ):
134+ # P15-H1: ``_origin_jit`` keeps the explicit predict setting so that
135+ # subclasses reading ``self._origin_jit.get('predict')`` see the user value.
136+ d = {C .PREDICT_PHASE : False , 'fit' : True }
137+ r = Runner (target , monitors = None , jit = d , progress_bar = False )
138+ assert r ._origin_jit .get (C .PREDICT_PHASE ) is False
139+ assert r .jit [C .PREDICT_PHASE ] is False
140+
141+
125142# --------------------------------------------------------------------------- #
126143# default / None monitors
127144# --------------------------------------------------------------------------- #
@@ -224,32 +241,34 @@ def test_dict_monitor_variable_value(target):
224241 assert var is target .V and idx is None
225242
226243
227- def test_dict_monitor_str_value_not_resolved ():
228- # NOTE: DEFECT - dict-form *string* monitors are NOT resolved to their
229- # target Variable. ``_format_dict_monitors`` wraps a string value 'V' into
230- # the tuple ('V', None); by the time it reaches
231- # ``_find_dict_monitor_targets`` the value is a tuple, so the
232- # ``isinstance(_mon, str)`` resolution branch (runner.py lines ~241-266) is
233- # never taken and the value falls through to the ``else`` branch which
234- # stores it verbatim. The recorded "variable" is therefore the literal
235- # string 'V', not ``target.V``. (Sequence-form monitors resolve correctly.)
244+ def test_dict_monitor_str_value_resolved ():
245+ # P15-H2 (was a documented DEFECT): dict-form *string* monitors must resolve
246+ # to their target Variable, exactly like sequence-form monitors.
236247 target = _Target ()
237248 r = Runner (target , monitors = {'a' : 'V' }, progress_bar = False , jit = False )
238249 var , idx = r ._monitors ['a' ]
239- assert var == 'V' # the bug: a string, not the Variable
250+ assert var is target . V # now resolved to the Variable
240251 assert idx is None
241252
242253
243- def test_dict_monitor_str_value_nested_not_resolved ():
244- # NOTE: DEFECT (same root cause as above) - a dotted string value is also
245- # stored verbatim and never resolved to ``target.sub.V``.
254+ def test_dict_monitor_str_value_nested_resolved ():
255+ # P15-H2: a dotted string value resolves to the nested ``target.sub.V``.
246256 target = _Target ()
247257 r = Runner (target , monitors = {'a' : 'sub.V' }, progress_bar = False , jit = False )
248258 var , idx = r ._monitors ['a' ]
249- assert var == ' sub.V'
259+ assert var is target . sub .V
250260 assert idx is None
251261
252262
263+ def test_dict_monitor_str_with_index_resolved ():
264+ # P15-H2: ``(name, index)`` dict values resolve the name and keep the index.
265+ target = _Target ()
266+ r = Runner (target , monitors = {'a' : ('V' , 2 )}, progress_bar = False , jit = False )
267+ var , idx = r ._monitors ['a' ]
268+ assert var is target .V
269+ assert np .asarray (bm .as_jax (idx )).tolist () == [2 ]
270+
271+
253272def test_dict_monitor_var_index_tuple (target ):
254273 r = Runner (target , monitors = {'a' : (target .spike , 0 )}, progress_bar = False , jit = False )
255274 _ , idx = r ._monitors ['a' ]
@@ -274,15 +293,12 @@ def test_dict_monitor_callable_value(target):
274293 assert r ._monitors ['a' ] is fn
275294
276295
277- def test_dict_monitor_str_missing_var_not_validated ():
278- # NOTE: DEFECT (same root cause) - because dict string monitors are never
279- # resolved, an *invalid* variable name like 'nope' is silently accepted and
280- # stored verbatim instead of raising RunningError (contrast with the
281- # sequence-form which validates and raises). See
282- # ``test_dict_monitor_str_value_not_resolved``.
296+ def test_dict_monitor_str_missing_var_raises ():
297+ # P15-H2 (was a documented DEFECT): an invalid variable name in a dict-form
298+ # string monitor must now be validated and raise, like the sequence form.
283299 target = _Target ()
284- r = Runner ( target , monitors = { 'a' : 'nope' }, progress_bar = False , jit = False )
285- assert r . _monitors [ 'a' ] == ( 'nope' , None )
300+ with pytest . raises ( RunningError ):
301+ Runner ( target , monitors = { 'a' : 'nope' }, progress_bar = False , jit = False )
286302
287303
288304def test_dict_monitor_nonstr_key_raises (target ):
@@ -349,29 +365,24 @@ def test_find_dict_monitor_targets_type_guard(target):
349365 r ._find_dict_monitor_targets (['not' , 'a' , 'dict' ])
350366
351367
352- def test_find_dict_monitor_targets_bare_string_branch ():
353- # NOTE: DEFECT - the ``isinstance(_mon, str)`` branch in
354- # ``_find_dict_monitor_targets`` is dead under normal use (see
355- # ``test_dict_monitor_str_value_not_resolved``) AND broken: it does
356- # ``key, index = _mon[0], _mon[1]`` which takes the first *two characters*
357- # of the string rather than treating the whole string as the variable name.
358- # We call the helper directly to exercise this branch. With the bare string
359- # 'Vx', key='V' (a real single-char variable) and index='x'.
368+ def test_find_dict_monitor_targets_name_tuple_branch ():
369+ # P15-H2: the resolution branch fires for a ``(name_str, index)`` tuple
370+ # (the form produced by ``_format_dict_monitors`` for string monitors) and
371+ # resolves the whole name to the Variable, preserving the key and index.
360372 target = _Target ()
361373 r = Runner (target , monitors = None , progress_bar = False , jit = False )
362- out = r ._find_dict_monitor_targets ({'k' : 'Vx' })
363- var , index = out ['V ' ] # NOTE: keyed by 'V' ( first char), not 'k '
374+ out = r ._find_dict_monitor_targets ({'k' : ( 'V' , None ) })
375+ var , index = out ['k ' ] # keyed by 'k', not by the first char of 'V '
364376 assert var is target .V
365- assert index == 'x' # NOTE: second char taken as the "index"
377+ assert index is None
366378
367379
368- def test_find_dict_monitor_targets_bare_string_missing_var_raises ():
369- # NOTE: DEFECT - same broken branch: a string whose first character is not
370- # an attribute of the target raises RunningError.
380+ def test_find_dict_monitor_targets_name_tuple_missing_var_raises ():
381+ # P15-H2: a ``(name, index)`` tuple whose name is unknown raises RunningError.
371382 target = _Target ()
372383 r = Runner (target , monitors = None , progress_bar = False , jit = False )
373384 with pytest .raises (RunningError ):
374- r ._find_dict_monitor_targets ({'k' : 'zz' })
385+ r ._find_dict_monitor_targets ({'k' : ( 'zz' , None ) })
375386
376387
377388# --------------------------------------------------------------------------- #
0 commit comments