Skip to content

Commit 092d2f1

Browse files
committed
Update while_loop audit test for restored idiom; force matplotlib Agg
- tests/audit/test_object_transform_fixes.py: #830 added M-06 asserting a None-returning while_loop body raises, but that broke the canonical brainpy idiom (body mutates Variable state, returns None) used by real models like SpikeTimeGroup. Now that the wrapper threads operands through unchanged, rewrite the test to assert that idiom works (state-driven termination) and update the module docstring. - conftest.py (new, repo root): force matplotlib onto the non-interactive Agg backend for both test roots (tests/ and brainpy/) so analysis tests that call pyplot.show() never open GUI windows, locally or in CI. - CI-models.yml: set MPLBACKEND=Agg on the pytest steps to match CI.yml.
1 parent e656dcc commit 092d2f1

3 files changed

Lines changed: 31 additions & 9 deletions

File tree

.github/workflows/CI-models.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
3737
pip install -e .
3838
- name: Test with pytest
39+
env:
40+
MPLBACKEND: Agg # Use non-interactive backend for matplotlib
3941
run: |
4042
pytest tests/
4143
@@ -58,6 +60,8 @@ jobs:
5860
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
5961
pip install -e .
6062
- name: Test with pytest
63+
env:
64+
MPLBACKEND: Agg # Use non-interactive backend for matplotlib
6165
run: |
6266
pytest tests/
6367
@@ -80,5 +84,7 @@ jobs:
8084
python -m pip install -r requirements-dev.txt
8185
pip install -e .
8286
- name: Test with pytest
87+
env:
88+
MPLBACKEND: Agg # Use non-interactive backend for matplotlib
8389
run: |
8490
python -m pytest tests/

conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# -*- coding: utf-8 -*-
2+
"""Pytest configuration shared by both test roots (``tests/`` and ``brainpy/``).
3+
4+
Force matplotlib onto the non-interactive ``Agg`` backend so that tests which
5+
exercise the analysis/plotting code paths (e.g. phase-plane and bifurcation
6+
analyses that call ``pyplot.show()``) never try to open a GUI window. This keeps
7+
the suite headless and non-blocking locally and in CI regardless of the
8+
``MPLBACKEND`` environment variable.
9+
"""
10+
11+
import matplotlib
12+
13+
matplotlib.use('Agg', force=True)

tests/audit/test_object_transform_fixes.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
accept a ``Variable``/``Array`` in ``operands``), H-03 (``for_loop(jit=False)``
1616
with a zero-length pytree operand returns ``[]`` instead of crashing),
1717
M-03 (``scan`` returns ``(carry, ys)``), M-05 (``ifelse`` builds mutually
18-
exclusive conditions), M-06 (``while_loop`` body returning ``None`` raises).
18+
exclusive conditions), M-06 (``while_loop`` body returning ``None`` threads the
19+
operands through unchanged so the canonical state-mutation idiom keeps working).
1920
* ``function.py`` — ``Partial``/``to_object`` behaviour and L-04 (``function``
2021
emits a ``DeprecationWarning``).
2122
* ``_utils.py`` — ``warp_to_no_state_input_output`` strips/restores states.
@@ -352,16 +353,18 @@ def body_f(x, y):
352353
assert len(res) == 2
353354

354355

355-
def test_while_loop_body_returning_none_raises():
356-
"""M-06: a ``while_loop`` body that returns ``None`` would freeze the carry
357-
and loop forever -- it must raise a clear ``ValueError`` instead."""
356+
def test_while_loop_body_returning_none_threads_operands():
357+
"""M-06: a ``while_loop`` body that returns ``None`` mutates ``Variable`` state
358+
in place (the canonical brainpy idiom, e.g. ``SpikeTimeGroup.update`` with empty
359+
``operands``) and threads the operands through unchanged. brainstate tracks the
360+
mutated state, which drives the loop condition, so it must NOT raise."""
361+
a = bm.Variable(bm.zeros(1))
358362

359-
def body(x):
360-
# returns None -> illegal
361-
pass
363+
def body():
364+
a.value += 1.
362365

363-
with pytest.raises(ValueError):
364-
bm.while_loop(body, lambda x: x < 3., 0.)
366+
bm.while_loop(body, lambda: bm.all(a.value < 3.), ())
367+
assert float(np.asarray(a.value[0])) == 3.0
365368

366369

367370
def test_ifelse_callable_branches_mutually_exclusive():

0 commit comments

Comments
 (0)