From 4508b7fc854a47a570d909f81f6eabc4b3b814b9 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:03:49 -0400 Subject: [PATCH 01/24] MAINT: Remove workaround for old PySide6 [circle full] --- .circleci/config.yml | 3 ++- azure-pipelines.yml | 4 ++-- mne/conftest.py | 2 ++ mne/gui/tests/test_gui_api.py | 8 -------- tools/azure_dependencies.sh | 2 +- tools/circleci_bash_env.sh | 1 + tools/circleci_dependencies.sh | 2 +- tools/github_actions_env_vars.sh | 4 ++-- 8 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c0780cd047f..d032e40887d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -131,7 +131,8 @@ jobs: - run: name: Check Qt command: | - ./tools/check_qt_import.sh PyQt6 + set -x + ./tools/check_qt_import.sh ${MNE_QT_BACKEND} # Load tiny cache so that ~/.mne does not need to be created below - restore_cache: keys: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f8e812ecc3e..3e3b5b18c89 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -89,7 +89,7 @@ stages: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' OMP_NUM_THREADS: '1' - MNE_TEST_ALLOW_SKIP: '^.*(PySide6 causes segfaults).*$' + MNE_TEST_ALLOW_SKIP: '^$' # nothing MNE_BROWSER_PRECOMPUTE: 'false' steps: - bash: | @@ -149,7 +149,7 @@ stages: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/gui mne/report" - MNE_TEST_ALLOW_SKIP: '^.*(PySide6 causes segfaults).*$' + MNE_TEST_ALLOW_SKIP: '^$' # nothing steps: - bash: ./tools/setup_xvfb.sh displayName: 'Install Ubuntu dependencies' diff --git a/mne/conftest.py b/mne/conftest.py index 2c2ee028b79..ccc953f85b9 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -1145,7 +1145,9 @@ def numba_conditional(monkeypatch, request): @pytest.fixture(scope="session") def _nbclient(): try: + import jupyter # noqa import nbformat + import nest_asyncio2 # noqa import trame # noqa from ipywidgets import Button # noqa from jupyter_client import AsyncKernelManager diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 5be82d27af6..6cc354ba30d 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -4,8 +4,6 @@ import pytest -from mne.utils import _check_qt_version - # These will skip all tests in this scope pytest.importorskip("nibabel") @@ -382,10 +380,4 @@ def _check_widget_trigger( def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" - _, api = _check_qt_version(return_api=True) - # TODO: After merging https://github.com/mne-tools/mne-python/pull/11567 - # The Qt CI run started failing about 50% of the time, so let's skip this - # for now. - if api == "PySide6": - pytest.skip("PySide6 causes segfaults on CIs sometimes") test_gui_api(None, None, backend="qt") diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index b30ae802cf6..fb14e85e85d 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -9,7 +9,7 @@ if [ "${TEST_MODE}" == "pip" ]; then elif [ "${TEST_MODE}" == "pip-pre" ]; then ${SCRIPT_DIR}/install_pre_requirements.sh python -m pip install $STD_ARGS --pre -e . --group=test_extra - echo "##vso[task.setvariable variable=MNE_TEST_ALLOW_SKIP].*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*" + echo "##vso[task.setvariable variable=MNE_TEST_ALLOW_SKIP].*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL).*" else echo "Unknown run type ${TEST_MODE}" exit 1 diff --git a/tools/circleci_bash_env.sh b/tools/circleci_bash_env.sh index fecb9627d4c..91df8a4f1b7 100755 --- a/tools/circleci_bash_env.sh +++ b/tools/circleci_bash_env.sh @@ -13,6 +13,7 @@ echo "set -o pipefail" >> $BASH_ENV echo "export XDG_RUNTIME_DIR=/tmp/runtime-circleci" >> $BASH_ENV echo "export MNE_FULL_DATE=true" >> $BASH_ENV echo "export MNE_3D_BACKEND=pyvistaqt" >> $BASH_ENV +echo "export MNE_QT_BACKEND=PySide6" >> $BASH_ENV echo "export MNE_BROWSER_BACKEND=qt" >> $BASH_ENV echo "export MNE_BROWSER_PRECOMPUTE=false" >> $BASH_ENV echo "export MNE_ADD_CONTRIBUTOR_IMAGE=true" >> $BASH_ENV diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 4b231368101..35a3d2ae75d 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -6,7 +6,7 @@ set -x python -m pip install --upgrade "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them python -m pip install --upgrade --progress-bar off $ONLY_BINARY \ - -ve .[full] \ + -ve .[full-pyside6] \ --group=test \ --group=doc-full \ "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index cf4d6eaa3c0..306eb320092 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -8,7 +8,7 @@ if [[ "$MNE_CI_KIND" == "pip"* ]] || [[ "$MNE_CI_KIND" == "minimal" ]]; then # We should test an eager import somewhere, might as well be here echo "EAGER_IMPORT=true" | tee -a $GITHUB_ENV # Make sure nothing unexpected is skipped - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Numba not|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Numba not).*" | tee -a $GITHUB_ENV fi echo "MNE_QT_BACKEND=PySide6" | tee -a $GITHUB_ENV elif [[ "$MNE_CI_KIND" == "old" ]]; then @@ -19,7 +19,7 @@ elif [[ "$MNE_CI_KIND" == "conda" ]]; then echo "Setting conda env vars for $MNE_CI_KIND" echo "CONDA_ENV=environment.yml" | tee -a $GITHUB_ENV echo "MNE_LOGGING_LEVEL=warning" | tee -a $GITHUB_ENV - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|PySide6 causes segfaults|Accelerate|Flakey verbose behavior).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Accelerate|Flakey verbose behavior).*" | tee -a $GITHUB_ENV # Our cache_dir test has problems when the path is too long, so prevent it from getting too long if [[ "$CI_OS_NAME" == "macos"* ]]; then echo "PYTEST_DEBUG_TEMPROOT=/tmp" | tee -a $GITHUB_ENV From de8851235d8d69bb2e9ee00f00fe91581aa422ba Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:21:46 -0400 Subject: [PATCH 02/24] FIX: Try [circle full] --- azure-pipelines.yml | 14 +++++++------- tools/azure_dependencies.sh | 2 +- tools/circleci_dependencies.sh | 2 +- tools/github_actions_dependencies.sh | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3e3b5b18c89..15f8de8c35f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,7 +52,7 @@ stages: - bash: | set -eo pipefail python -m pip install --progress-bar off --upgrade pip build - python -m pip install --progress-bar off -ve .[hdf5] --group=test + python -m pip install --only-binary=":all:" --progress-bar off -ve .[hdf5] --group=test python -m pip uninstall -yq pytest-qt # don't want to set up display, etc. for this pre-commit install --install-hooks displayName: Install dependencies @@ -114,9 +114,9 @@ stages: - bash: | set -eo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef + python -m pip install --progress-bar off --only-binary=":all:" "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef python -m pip uninstall -yq mne - python -m pip install --progress-bar off --upgrade -e . --group=test + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test displayName: 'Install dependencies with pip' - bash: | set -eo pipefail @@ -155,16 +155,16 @@ stages: displayName: 'Install Ubuntu dependencies' - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.14' architecture: 'x64' addToPath: true displayName: 'Get Python' - bash: | set -eo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off --upgrade --pre --only-binary=\"numpy,scipy,matplotlib,vtk\" numpy scipy matplotlib vtk + python -m pip install --progress-bar off --upgrade --pre --only-binary=":all:" numpy scipy matplotlib vtk python -c "import vtk" - python -m pip install --progress-bar off --upgrade -ve .[full] --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" + python -m pip install --progress-bar off --upgrade -ve .[full] --only-binary=":all:" --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" displayName: 'Install dependencies with pip' - bash: | set -e @@ -252,7 +252,7 @@ stages: displayName: Install OpenGL - bash: ./tools/azure_dependencies.sh displayName: Install dependencies with pip - - script: pip install -e . + - script: pip install --only-binary=":all:" -e . displayName: 'Install MNE-Python dev' - script: mne sys_info -pd displayName: 'Print config' diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index fb14e85e85d..010992231f7 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -2,7 +2,7 @@ set -eo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -STD_ARGS="--progress-bar off --upgrade " +STD_ARGS="--progress-bar off --upgrade --only-binary=:all:" python -m pip install $STD_ARGS pip setuptools wheel if [ "${TEST_MODE}" == "pip" ]; then python -m pip install $STD_ARGS --only-binary="numba,llvmlite,numpy,scipy,vtk,dipy,openmeeg" -e .[full] --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 35a3d2ae75d..0dc9d3b328b 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -1,6 +1,6 @@ #!/bin/bash -ef -ONLY_BINARY="--only-binary \"numpy,dipy,scipy,matplotlib,pandas,statsmodels,netCDF4,h5py\"" +ONLY_BINARY=":all:" set -x python -m pip install --upgrade "pip>=25.1" build diff --git a/tools/github_actions_dependencies.sh b/tools/github_actions_dependencies.sh index 3ee5986cfa9..09ebb655f61 100755 --- a/tools/github_actions_dependencies.sh +++ b/tools/github_actions_dependencies.sh @@ -3,7 +3,7 @@ set -eo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -ONLY_BINARY_ARG="--only-binary=numpy,scipy,matplotlib,numba,llvmlite,antio" +ONLY_BINARY_ARG="--only-binary=:all:" STD_ARGS="--progress-bar off --upgrade" INSTALL_ARGS="-e" if [ ! -z "$CONDA_ENV" ]; then From 2415e79af5d86a7f5ea3565cd0aa773fc22542f6 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:23:53 -0400 Subject: [PATCH 03/24] FIX: Where [circle full] --- tools/circleci_dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 0dc9d3b328b..50f41c1a096 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -1,6 +1,6 @@ #!/bin/bash -ef -ONLY_BINARY=":all:" +ONLY_BINARY="--only-binary=:all:" set -x python -m pip install --upgrade "pip>=25.1" build From 9e8ff4c2c8870762b5e3e9cf944ec1857e740f0d Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:29:01 -0400 Subject: [PATCH 04/24] FIX: More [circle full] --- tools/circleci_dependencies.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 50f41c1a096..abe7d257b8d 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -3,17 +3,19 @@ ONLY_BINARY="--only-binary=:all:" set -x -python -m pip install --upgrade "pip>=25.1" build +python -m pip install --upgrade $ONLY_BINARY "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them -python -m pip install --upgrade --progress-bar off $ONLY_BINARY \ +# and the interface doesn't have wheels so allow it to build +python -m pip install --upgrade \ + "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ +python -m pip install --upgrade $ONLY_BINARY \ -ve .[full-pyside6] \ --group=test \ --group=doc-full \ - "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ -r doc/sphinxext/related_software.txt \ "git+https://github.com/mne-tools/mne-bids.git" \ "git+https://github.com/mne-tools/mne-qt-browser.git" \ "git+https://github.com/pyvista/pyvista.git" \ "git+https://github.com/sphinx-gallery/sphinx-gallery.git" -python -m pip install --upgrade --progress-bar off --no-deps $ONLY_BINARY \ +python -m pip install --upgrade --no-deps $ONLY_BINARY \ -r doc/sphinxext/related_software_nodeps.txt From 2bd8ba2036474955c892652fd843ee641783c247 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:34:37 -0400 Subject: [PATCH 05/24] FIX: More [circle full] --- tools/circleci_dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index abe7d257b8d..84275767d5c 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -7,7 +7,7 @@ python -m pip install --upgrade $ONLY_BINARY "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them # and the interface doesn't have wheels so allow it to build python -m pip install --upgrade \ - "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ + "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" python -m pip install --upgrade $ONLY_BINARY \ -ve .[full-pyside6] \ --group=test \ From b17e507f7bf8ab9e52eb793e16353f3e10882bdf Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:47:41 -0400 Subject: [PATCH 06/24] FIX: Circle [circle full] --- doc/sphinxext/related_software.txt | 2 +- doc/sphinxext/related_software_nodeps.txt | 12 ++++++------ tools/circleci_dependencies.sh | 21 ++++++++++----------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/doc/sphinxext/related_software.txt b/doc/sphinxext/related_software.txt index 2c38c82c089..fea75e73029 100644 --- a/doc/sphinxext/related_software.txt +++ b/doc/sphinxext/related_software.txt @@ -3,12 +3,12 @@ alphaCSC autoreject bycycle -conpy curryreader dcm2niix eeg_positions emd fooof +hedtools meegkit meggie mne-ari diff --git a/doc/sphinxext/related_software_nodeps.txt b/doc/sphinxext/related_software_nodeps.txt index 2b2e0c68945..e9ab63687dc 100644 --- a/doc/sphinxext/related_software_nodeps.txt +++ b/doc/sphinxext/related_software_nodeps.txt @@ -1,7 +1,7 @@ -# deps with onerous requirements (tensorflow, wxPython, mayavi, opencv) -cross-domain-saliency-maps -fsleyes -mne-kit-gui -mne-videobrowser -hedtools # because it limits our Pandas version currently +# deps with onerous requirements +conpy # requires future and subprocess32, which don't have wheels +cross-domain-saliency-maps # tensorflow +fsleyes # wxPython +mne-kit-gui # mayavi +mne-videobrowser # opencv zuna # requires pytorch diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 84275767d5c..dbbcec626fb 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -1,21 +1,20 @@ #!/bin/bash -ef -ONLY_BINARY="--only-binary=:all:" +ONLY_BINARY="--only-binary=:all: --no-binary=mne-ari,rpy2-interface" set -x -python -m pip install --upgrade $ONLY_BINARY "pip>=25.1" build +python -m pip install --upgrade "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them -# and the interface doesn't have wheels so allow it to build -python -m pip install --upgrade \ - "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" -python -m pip install --upgrade $ONLY_BINARY \ +python -m pip install --upgrade --progress-bar off $ONLY_BINARY \ -ve .[full-pyside6] \ --group=test \ --group=doc-full \ + "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ -r doc/sphinxext/related_software.txt \ - "git+https://github.com/mne-tools/mne-bids.git" \ - "git+https://github.com/mne-tools/mne-qt-browser.git" \ - "git+https://github.com/pyvista/pyvista.git" \ - "git+https://github.com/sphinx-gallery/sphinx-gallery.git" -python -m pip install --upgrade --no-deps $ONLY_BINARY \ + "mne-bids @ https://github.com/mne-tools/mne-bids/archive/refs/heads/main.zip" \ + "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" \ + "pyvista @ https://github.com/pyvista/pyvista/archive/refs/heads/main.zip" \ + "pyvistaqt @ https://github.com/pyvista/pyvistaqt/archive/refs/heads/main.zip" \ + "sphinx-gallery @ https://github.com/sphinx-gallery/sphinx-gallery/archive/refs/heads/master.zip" +python -m pip install --upgrade --progress-bar off --no-deps $ONLY_BINARY \ -r doc/sphinxext/related_software_nodeps.txt From 83c7ac1606d60555b3b960fe4f58109d47d6be98 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:51:45 -0400 Subject: [PATCH 07/24] FIX: Order [circle full] --- tools/circleci_dependencies.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index dbbcec626fb..03ab782b642 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -1,20 +1,19 @@ #!/bin/bash -ef -ONLY_BINARY="--only-binary=:all: --no-binary=mne-ari,rpy2-interface" - set -x python -m pip install --upgrade "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them -python -m pip install --upgrade --progress-bar off $ONLY_BINARY \ +python -m pip install --upgarde --only-binary=numpy,scipy \ + "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" mne-ari +python -m pip install --upgrade --only-binary=:all: \ -ve .[full-pyside6] \ --group=test \ --group=doc-full \ - "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" \ - -r doc/sphinxext/related_software.txt \ "mne-bids @ https://github.com/mne-tools/mne-bids/archive/refs/heads/main.zip" \ "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" \ "pyvista @ https://github.com/pyvista/pyvista/archive/refs/heads/main.zip" \ "pyvistaqt @ https://github.com/pyvista/pyvistaqt/archive/refs/heads/main.zip" \ - "sphinx-gallery @ https://github.com/sphinx-gallery/sphinx-gallery/archive/refs/heads/master.zip" -python -m pip install --upgrade --progress-bar off --no-deps $ONLY_BINARY \ + "sphinx-gallery @ https://github.com/sphinx-gallery/sphinx-gallery/archive/refs/heads/master.zip" \ + -r doc/sphinxext/related_software.txt +python -m pip install --upgrade --no-deps --only-binary=:all: \ -r doc/sphinxext/related_software_nodeps.txt From a93096bf8b8a23fab2f5c2bb377efe0f54038544 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 13:52:31 -0400 Subject: [PATCH 08/24] FIX: Order [circle full] --- tools/circleci_dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/circleci_dependencies.sh b/tools/circleci_dependencies.sh index 03ab782b642..935c7842025 100755 --- a/tools/circleci_dependencies.sh +++ b/tools/circleci_dependencies.sh @@ -3,7 +3,7 @@ set -x python -m pip install --upgrade "pip>=25.1" build # rpy2 3.6.7 (or its deps) cause problems with our installed R version, so pin them -python -m pip install --upgarde --only-binary=numpy,scipy \ +python -m pip install --upgrade --only-binary=numpy,scipy \ "rpy2==3.6.6" "rpy2-rinterface==3.6.5" "rpy2-robjects==3.6.4" mne-ari python -m pip install --upgrade --only-binary=:all: \ -ve .[full-pyside6] \ From 58316e9496f712c3f5e278d86913c7433605e5f7 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 14:34:28 -0400 Subject: [PATCH 09/24] Cleaner --- mne/conftest.py | 18 +++++++----------- mne/gui/tests/test_gui_api.py | 4 ++-- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/mne/conftest.py b/mne/conftest.py index ccc953f85b9..8f92d205d11 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -47,7 +47,6 @@ numerics, ) from mne.viz._figure import use_browser_backend -from mne.viz.backends._utils import _init_mne_qtapp # data from sample dataset test_path = testing.data_path(download=False) @@ -751,7 +750,7 @@ def _check_skip_backend(name): @pytest.fixture(scope="session") -def pixel_ratio(): +def pixel_ratio(qapp): """Get the pixel ratio.""" # _check_qt_version will init an app for us, so no need for us to do it if not check_version("pyvista", "0.32") or not _check_qt_version(): @@ -759,8 +758,7 @@ def pixel_ratio(): from qtpy.QtCore import Qt from qtpy.QtWidgets import QMainWindow - app = _init_mne_qtapp() - app.processEvents() + qapp.processEvents() window = QMainWindow() window.setAttribute(Qt.WA_DeleteOnClose, True) ratio = float(window.devicePixelRatio()) @@ -1260,24 +1258,22 @@ def nirx_snirf(request): @pytest.fixture -def qt_windows_closed(request): +def qt_windows_closed(request, qapp, qtbot): """Ensure that no new Qt windows are open after a test.""" _check_skip_backend("pyvistaqt") - app = _init_mne_qtapp() - - app.processEvents() + qapp.processEvents() gc.collect() - n_before = len(app.topLevelWidgets()) + n_before = len(qapp.topLevelWidgets()) marks = set(mark.name for mark in request.node.iter_markers()) yield - app.processEvents() + qtbot.wait(100) # give things time to close gc.collect() if "allow_unclosed" in marks: return # Don't check when the test fails if not _test_passed(request): return - widgets = app.topLevelWidgets() + widgets = qapp.topLevelWidgets() n_after = len(widgets) assert n_before == n_after, widgets[-4:] diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 6cc354ba30d..4e1e1708eba 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -8,7 +8,7 @@ pytest.importorskip("nibabel") -def test_gui_api(renderer_notebook, nbexec, *, backend="qt"): +def test_gui_api_notebook(renderer_notebook, nbexec, *, backend="qt"): """Test GUI API.""" import contextlib import warnings @@ -380,4 +380,4 @@ def _check_widget_trigger( def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" - test_gui_api(None, None, backend="qt") + test_gui_api_notebook(None, None, backend="qt") From 90db83ef0cbc0edb09515e007ab40f278111b7ea Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 14:59:48 -0400 Subject: [PATCH 10/24] FIX: Speed up --- mne/viz/_brain/tests/test_brain.py | 196 +++++++++++++++-------------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/mne/viz/_brain/tests/test_brain.py b/mne/viz/_brain/tests/test_brain.py index 7f0d178e538..212f300c81b 100644 --- a/mne/viz/_brain/tests/test_brain.py +++ b/mne/viz/_brain/tests/test_brain.py @@ -118,9 +118,6 @@ def GetPosition(self): return np.array(self.GetPickPosition()) - (0, 0, 100) -# TODO: allow_unclosed for macOS here as the conda and M1 builds show some -# windows stay open afterward -@pytest.mark.allow_unclosed def test_layered_mesh(renderer_interactive_pyvistaqt): """Test management of scalars/colormap overlay.""" mesh = _LayeredMesh( @@ -574,12 +571,46 @@ def __init__(self): brain.close() -# TODO: Figure out why brain_gc is problematic here on PyQt5 -@pytest.mark.allow_unclosed @testing.requires_testing_data -@pytest.mark.parametrize( - "sensor_colors, sensor_scales, expectation", - [ +def test_add_sensors_scales(renderer_interactive_pyvistaqt): + """Test sensor_scales parameter.""" + kwargs = dict(subject=subject, subjects_dir=subjects_dir) + hemi = "lh" + surf = "white" + cortex = "low_contrast" + title = "test" + size = (300, 300) + + brain = Brain( + hemi=hemi, + surf=surf, + size=size, + title=title, + cortex=cortex, + units="m", + silhouette=dict(decimate=0.95), + **kwargs, + ) + + proj_info = create_info([f"Ch{i}" for i in range(1, 7)], 1000, "seeg") + pos = ( + np.array( + [ + [25.85, 9.04, -5.38], + [33.56, 9.04, -5.63], + [40.44, 9.04, -5.06], + [46.75, 9.04, -6.78], + [-30.08, 9.04, 28.23], + [-32.95, 9.04, 37.99], + ] + ) + / 1000 + ) + proj_info.set_montage( + make_dig_montage(ch_pos=dict(zip(proj_info.ch_names, pos)), coord_frame="head") + ) + + for sensor_colors, sensor_scales, expectation in [ ( {"seeg": ["k"] * 5}, {"seeg": [2] * 6}, @@ -630,57 +661,14 @@ def __init__(self): {"seeg": [2] * 6}, nullcontext(), ), - ], -) -def test_add_sensors_scales( - renderer_interactive_pyvistaqt, - sensor_colors, - sensor_scales, - expectation, -): - """Test sensor_scales parameter.""" - kwargs = dict(subject=subject, subjects_dir=subjects_dir) - hemi = "lh" - surf = "white" - cortex = "low_contrast" - title = "test" - size = (300, 300) - - brain = Brain( - hemi=hemi, - surf=surf, - size=size, - title=title, - cortex=cortex, - units="m", - silhouette=dict(decimate=0.95), - **kwargs, - ) - - proj_info = create_info([f"Ch{i}" for i in range(1, 7)], 1000, "seeg") - pos = ( - np.array( - [ - [25.85, 9.04, -5.38], - [33.56, 9.04, -5.63], - [40.44, 9.04, -5.06], - [46.75, 9.04, -6.78], - [-30.08, 9.04, 28.23], - [-32.95, 9.04, 37.99], - ] - ) - / 1000 - ) - proj_info.set_montage( - make_dig_montage(ch_pos=dict(zip(proj_info.ch_names, pos)), coord_frame="head") - ) - with expectation: - brain.add_sensors( - proj_info, - trans=fname_trans, - sensor_colors=sensor_colors, - sensor_scales=sensor_scales, - ) + ]: + with expectation: + brain.add_sensors( + proj_info, + trans=fname_trans, + sensor_colors=sensor_colors, + sensor_scales=sensor_scales, + ) brain.close() @@ -989,25 +977,18 @@ def test_brain_time_viewer(renderer_interactive_pyvistaqt, pixel_ratio, brain_gc @testing.requires_testing_data @pytest.mark.parametrize( - "hemi", + "hemi, src", [ - "lh", - pytest.param("rh", marks=pytest.mark.slowtest), - pytest.param("split", marks=pytest.mark.slowtest), - pytest.param("both", marks=pytest.mark.slowtest), + ("lh", "surface"), + pytest.param("rh", "surface", marks=pytest.mark.slowtest), + pytest.param("split", "surface", marks=pytest.mark.slowtest), + pytest.param("both", "surface", marks=pytest.mark.slowtest), + pytest.param("lh", "vector", marks=pytest.mark.slowtest), + pytest.param("rh", "volume", marks=pytest.mark.slowtest), + pytest.param("both", "mixed", marks=pytest.mark.slowtest), ], ) -@pytest.mark.parametrize( - "src", - [ - "surface", - pytest.param("vector", marks=pytest.mark.slowtest), - pytest.param("volume", marks=pytest.mark.slowtest), - pytest.param("mixed", marks=pytest.mark.slowtest), - ], -) -@pytest.mark.slowtest -def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain_gc): +def test_brain_traces_basic(renderer_interactive_pyvistaqt, hemi, src, brain_gc): """Test brain traces.""" hemi_str = list() if src in ("surface", "vector", "mixed"): @@ -1061,26 +1042,29 @@ def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain assert "extract_mode" not in brain.widgets brain.close() - # test colormap - if src != "vector": - brain = _create_testing_brain( - hemi=hemi, - surf="white", - src=src, - show_traces=0.5, - initial_time=0, - volume_options=None, # for speed, don't upsample - n_time=1 if src == "mixed" else 5, - diverging=True, - add_data_kwargs=dict(colorbar_kwargs=dict(n_labels=3)), - ) - # mne_analyze should be chosen - ctab = brain._data["ctable"] - assert_array_equal(ctab[0], [0, 255, 255, 255]) # opaque cyan - assert_array_equal(ctab[-1], [255, 255, 0, 255]) # opaque yellow - assert_allclose(ctab[len(ctab) // 2], [128, 128, 128, 0], atol=3) - brain.close() +@testing.requires_testing_data +@pytest.mark.parametrize( + "hemi, src", + [ + ("lh", "surface"), + pytest.param("rh", "surface", marks=pytest.mark.slowtest), + pytest.param("split", "surface", marks=pytest.mark.slowtest), + pytest.param("both", "surface", marks=pytest.mark.slowtest), + pytest.param("lh", "vector", marks=pytest.mark.slowtest), + pytest.param("rh", "volume", marks=pytest.mark.slowtest), + pytest.param("both", "mixed", marks=pytest.mark.slowtest), + ], +) +def test_brain_traces_vertex( + renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain_gc +): + """Test brain traces vertex mode.""" + hemi_str = list() + if src in ("surface", "vector", "mixed"): + hemi_str.extend([hemi] if hemi in ("lh", "rh") else ["lh", "rh"]) + if src in ("mixed", "volume"): + hemi_str.extend(["vol"]) # vertex traces brain = _create_testing_brain( hemi=hemi, @@ -1227,6 +1211,26 @@ def test_brain_traces(renderer_interactive_pyvistaqt, hemi, src, tmp_path, brain assert_allclose(img.shape[0], screenshot_all.shape[0], atol=1) +def test_brain_traces_colormap(renderer_interactive_pyvistaqt, brain_gc): + """Test colormap selection.""" + brain = _create_testing_brain( + hemi="lh", + surf="white", + src="surface", + show_traces=0.5, + initial_time=0, + n_time=5, + diverging=True, + add_data_kwargs=dict(colorbar_kwargs=dict(n_labels=3)), + ) + # mne_analyze should be chosen + ctab = brain._data["ctable"] + assert_array_equal(ctab[0], [0, 255, 255, 255]) # opaque cyan + assert_array_equal(ctab[-1], [255, 255, 0, 255]) # opaque yellow + assert_allclose(ctab[len(ctab) // 2], [128, 128, 128, 0], atol=3) + brain.close() + + # TODO: don't skip on Windows, see # https://github.com/mne-tools/mne-python/pull/10935 # for some reason there is a dependency issue with ipympl even using pyvista @@ -1511,14 +1515,12 @@ def _create_testing_brain( colormap="auto", clim=clim, src=sample_src, + smoothing_steps=0, **kwargs, ) return brain_data -# TODO: allow_unclosed for macOS here as the conda build shows some -# windows stay open afterward -@pytest.mark.allow_unclosed def test_foci_mapping(tmp_path, renderer_interactive_pyvistaqt): """Test mapping foci to the surface.""" tiny_brain, _ = tiny(tmp_path) From fec4f4881f568b97b883b35c212c3b2d84a7020c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 16:41:30 -0400 Subject: [PATCH 11/24] FIX: Twice --- mne/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/conftest.py b/mne/conftest.py index 8f92d205d11..b5f550f2486 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -1266,6 +1266,7 @@ def qt_windows_closed(request, qapp, qtbot): n_before = len(qapp.topLevelWidgets()) marks = set(mark.name for mark in request.node.iter_markers()) yield + gc.collect() qtbot.wait(100) # give things time to close gc.collect() if "allow_unclosed" in marks: From 3d37cf040682035a3187269ffbb659aa46d948e3 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 18:36:59 -0400 Subject: [PATCH 12/24] TST: Limit [skip circle] --- .github/workflows/tests.yml | 20 +------ azure-pipelines.yml | 105 +++++++++++------------------------- mne/conftest.py | 3 +- mne/viz/backends/_utils.py | 6 +-- 4 files changed, 34 insertions(+), 100 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5a8386ccd5..34e5b731513 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,27 +67,9 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-latest - python: '3.13' - kind: pip - os: ubuntu-latest python: '3.14' kind: pip-pre - - os: ubuntu-latest - python: '3.13' - kind: conda - - os: macos-latest # arm64 - python: '3.14' - kind: pip - - os: macos-15-intel # Intel - python: '3.13' - kind: pip - - os: ubuntu-latest - python: '3.12' - kind: minimal - - os: ubuntu-22.04 - python: '3.10' - kind: old steps: - uses: actions/checkout@v6 with: @@ -157,7 +139,7 @@ jobs: - run: bash ./tools/check_qt_import.sh $MNE_QT_BACKEND if: env.MNE_QT_BACKEND != '' - name: Run tests with no testing data - run: MNE_SKIP_TESTING_DATASET_TESTS=true pytest -m "not (ultraslowtest or pgtest)" --tb=short --cov=mne --cov-report xml -vv -rfE mne/ + run: MNE_SKIP_TESTING_DATASET_TESTS=true pytest -m "not (ultraslowtest or pgtest)" --tb=short --cov=mne --cov-report xml -vv -rfE mne/gui if: matrix.kind == 'minimal' - run: ./tools/get_testing_version.sh - uses: actions/cache@v5 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 15f8de8c35f..c223901acfa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,15 +38,11 @@ stages: - job: All pool: vmImage: 'ubuntu-latest' - variables: - PYTHON_VERSION: '3.11' - PYTHON_ARCH: 'x64' steps: - - bash: echo $(COMMIT_MSG) - task: UsePythonVersion@0 inputs: - versionSpec: $(PYTHON_VERSION) - architecture: $(PYTHON_ARCH) + versionSpec: '3.14' + architecture: 'x64' addToPath: true displayName: 'Get Python' - bash: | @@ -56,26 +52,17 @@ stages: python -m pip uninstall -yq pytest-qt # don't want to set up display, etc. for this pre-commit install --install-hooks displayName: Install dependencies - - bash: | - make pre-commit - displayName: make pre-commit + - bash: make pre-commit condition: always() - - bash: | - make nesting - displayName: make nesting + - bash: make nesting condition: always() - - bash: | - make check-readme - displayName: make check-readme + - bash: make check-readme condition: always() - bash: mypy - displayName: mypy condition: always() - bash: vulture - displayName: vulture condition: always() - - stage: Test condition: and(succeeded(), eq(dependencies.Check.outputs['Skip.result.start_main'], 'true')) dependsOn: ['Style', 'Check'] @@ -93,13 +80,11 @@ stages: MNE_BROWSER_PRECOMPUTE: 'false' steps: - bash: | - set -eo pipefail + set -xeo pipefail ./tools/setup_xvfb.sh sudo apt install -yq tcsh displayName: 'Install Ubuntu dependencies' - - bash: | - source tools/get_minimal_commands.sh - displayName: 'Install minimal commands' + - bash: ./tools/get_minimal_commands.sh - bash: | echo $PATH mne_surf2bem --version @@ -110,36 +95,26 @@ stages: versionSpec: '3.12' architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off --only-binary=":all:" "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef - python -m pip uninstall -yq mne - python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef displayName: 'Install dependencies with pip' + - bash: ./tools/check_qt_import.sh PySide6 - bash: | - set -eo pipefail + set -xeo pipefail mne sys_info -pd mne sys_info -pd | grep "qtpy .*(PySide6=.*)$" displayName: Print config - - bash: | - set -eo pipefail - LD_DEBUG=libs python -c "from PySide6.QtWidgets import QApplication, QWidget; app = QApplication([]); import matplotlib; matplotlib.use('QtAgg'); import matplotlib.pyplot as plt; plt.figure()" - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - displayName: 'Cache testing data' - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' - script: pytest -m "ultraslowtest or pgtest" --tb=short --cov=mne --cov-report=xml -vv mne displayName: 'slow and mne-qt-browser tests' - # Coverage - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' condition: succeededOrFailed() - job: Linux_Qt_Bindings @@ -148,59 +123,50 @@ stages: variables: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' - TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/gui mne/report" + # TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/report" + TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui" MNE_TEST_ALLOW_SKIP: '^$' # nothing steps: - bash: ./tools/setup_xvfb.sh - displayName: 'Install Ubuntu dependencies' - task: UsePythonVersion@0 inputs: versionSpec: '3.14' architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off --upgrade --pre --only-binary=":all:" numpy scipy matplotlib vtk + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e .[full-pyside6] --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" python -c "import vtk" - python -m pip install --progress-bar off --upgrade -ve .[full] --only-binary=":all:" --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" displayName: 'Install dependencies with pip' - bash: | - set -e + set -xeo pipefail which mne mne sys_info -pd python ./tools/check_mne_location.py displayName: Print config - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - displayName: 'Cache testing data' - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' + - bash: ./tools/check_qt_import.sh PySide6 - bash: | - set -eo pipefail - python -m pip install PyQt6 - LD_DEBUG=libs python -c "from PyQt6.QtWidgets import QApplication, QWidget; app = QApplication([]); import matplotlib; matplotlib.use('QtAgg'); import matplotlib.pyplot as plt; plt.figure()" - displayName: 'Check Qt import' - - bash: | - set -eo pipefail - mne sys_info -pd - mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" - PYTEST_QT_API=PyQt6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} - python -m pip uninstall -yq PyQt6 PyQt6-sip PyQt6-Qt6 - displayName: 'PyQt6' - - bash: | - set -eo pipefail + set -xeo pipefail python -m pip install "PySide6!=6.8.0,!=6.8.0.1,!=6.9.1" mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PySide6=.*)$" PYTEST_QT_API=PySide6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PySide6 displayName: 'PySide6' + - bash: | + set -xeo pipefail + mne sys_info -pd + mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" + PYTEST_QT_API=PyQt6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} + python -m pip uninstall -yq PyQt6 PyQt6-sip PyQt6-Qt6 + displayName: 'PyQt6' # PyQt5 leaves cruft behind, so run it last - bash: | set -eo pipefail @@ -210,7 +176,6 @@ stages: PYTEST_QT_API=PyQt5 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PyQt5 PyQt5-sip PyQt5-Qt5 displayName: 'PyQt5' - # Coverage - bash: bash <(curl -s https://codecov.io/bash) displayName: 'Codecov' condition: succeededOrFailed() @@ -224,9 +189,7 @@ stages: OPENBLAS_NUM_THREADS: '2' OMP_DYNAMIC: 'false' PYTHONUNBUFFERED: 1 - PYTHONIOENCODING: 'utf-8' AZURE_CI_WINDOWS: 'true' - PYTHON_ARCH: 'x64' MNE_CI_KIND: $(TEST_MODE) timeoutInMinutes: 95 strategy: @@ -242,35 +205,27 @@ stages: - task: UsePythonVersion@0 inputs: versionSpec: $(PYTHON_VERSION) - architecture: $(PYTHON_ARCH) + architecture: 'x64' addToPath: true - displayName: 'Get Python' - bash: | - set -eo pipefail + set -xeo pipefail git clone --depth 1 https://github.com/pyvista/setup-headless-display-action.git MESA3D_VERSION=24.3.0 bash setup-headless-display-action/windows/install_opengl.sh displayName: Install OpenGL - bash: ./tools/azure_dependencies.sh - displayName: Install dependencies with pip - script: pip install --only-binary=":all:" -e . - displayName: 'Install MNE-Python dev' - script: mne sys_info -pd - displayName: 'Print config' - script: python -c "import numpy; numpy.show_config()" - displayName: Print NumPy config - script: python -c "import numpy; import scipy.linalg; import sklearn.neighbors; from threadpoolctl import threadpool_info; from pprint import pprint; pprint(threadpool_info())" displayName: Print threadpoolctl info - - bash: source tools/get_testing_version.sh - displayName: 'Get testing version' + - bash: ./tools/get_testing_version.sh - task: Cache@2 inputs: key: $(testing_version) path: C:\Users\VssAdministrator\mne_data displayName: 'Cache testing data' - bash: ./tools/github_actions_download.sh - displayName: 'Get test data' - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne displayName: 'Run tests' - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' condition: succeededOrFailed() diff --git a/mne/conftest.py b/mne/conftest.py index b5f550f2486..c5ca124a43f 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -1266,8 +1266,7 @@ def qt_windows_closed(request, qapp, qtbot): n_before = len(qapp.topLevelWidgets()) marks = set(mark.name for mark in request.node.iter_markers()) yield - gc.collect() - qtbot.wait(100) # give things time to close + qapp.processEvents() gc.collect() if "allow_unclosed" in marks: return diff --git a/mne/viz/backends/_utils.py b/mne/viz/backends/_utils.py index b58d940439a..a22081879b1 100644 --- a/mne/viz/backends/_utils.py +++ b/mne/viz/backends/_utils.py @@ -399,13 +399,11 @@ def func(self, *args, **kwargs): continue parent = self name = attr.split(".")[-1] + assert name try: for n in attr.split(".")[:-1]: parent = getattr(parent, n) - if name: - widget = getattr(parent, name, False) - else: # empty string means "self" - widget = parent + widget = getattr(parent, name, False) if widget: widget.close() del widget From 6f2df6765f7647535a4fd0f818c09c9ada01fff6 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 18:44:02 -0400 Subject: [PATCH 13/24] FIX: Not waiting all day [skip circle] --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 34e5b731513..c0d9ce930b7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -147,7 +147,7 @@ jobs: key: ${{ env.TESTING_VERSION }} path: ~/mne_data - run: bash ./tools/github_actions_download.sh - - run: bash ./tools/github_actions_test.sh # for some reason on macOS we need to run "bash X" in order for a failed test run to show up + - run: pytest mne/gui # bash ./tools/github_actions_test.sh # for some reason on macOS we need to run "bash X" in order for a failed test run to show up - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} From 8720d204effe8e94c200900f955cb38da1dcaeba Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 18:55:35 -0400 Subject: [PATCH 14/24] FIX: Try more [skip circle] --- azure-pipelines.yml | 101 +------------------------------------ mne/viz/backends/_utils.py | 6 ++- 2 files changed, 5 insertions(+), 102 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c223901acfa..eba6f807094 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,62 +69,13 @@ stages: variables: AZURE_CI: 'true' jobs: - - job: Linux_Ultraslow_PG - pool: - vmImage: 'ubuntu-latest' - variables: - DISPLAY: ':99' - OPENBLAS_NUM_THREADS: '1' - OMP_NUM_THREADS: '1' - MNE_TEST_ALLOW_SKIP: '^$' # nothing - MNE_BROWSER_PRECOMPUTE: 'false' - steps: - - bash: | - set -xeo pipefail - ./tools/setup_xvfb.sh - sudo apt install -yq tcsh - displayName: 'Install Ubuntu dependencies' - - bash: ./tools/get_minimal_commands.sh - - bash: | - echo $PATH - mne_surf2bem --version - fsl_rigid_register --version - displayName: 'Test minimal commands' - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.12' - architecture: 'x64' - addToPath: true - - bash: | - set -xeo pipefail - python -m pip install --progress-bar off --upgrade pip - python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef - displayName: 'Install dependencies with pip' - - bash: ./tools/check_qt_import.sh PySide6 - - bash: | - set -xeo pipefail - mne sys_info -pd - mne sys_info -pd | grep "qtpy .*(PySide6=.*)$" - displayName: Print config - - bash: ./tools/get_testing_version.sh - - task: Cache@2 - inputs: - key: $(testing_version) - path: /home/vsts/mne_data - - bash: ./tools/github_actions_download.sh - - script: pytest -m "ultraslowtest or pgtest" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'slow and mne-qt-browser tests' - - bash: bash <(curl -s https://codecov.io/bash) - condition: succeededOrFailed() - - job: Linux_Qt_Bindings pool: vmImage: 'ubuntu-latest' variables: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' - # TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/report" - TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui" + TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/report" MNE_TEST_ALLOW_SKIP: '^$' # nothing steps: - bash: ./tools/setup_xvfb.sh @@ -179,53 +130,3 @@ stages: - bash: bash <(curl -s https://codecov.io/bash) displayName: 'Codecov' condition: succeededOrFailed() - - - job: Windows - pool: - vmImage: 'windows-latest' - variables: - MNE_LOGGING_LEVEL: 'warning' - MNE_FORCE_SERIAL: 'true' - OPENBLAS_NUM_THREADS: '2' - OMP_DYNAMIC: 'false' - PYTHONUNBUFFERED: 1 - AZURE_CI_WINDOWS: 'true' - MNE_CI_KIND: $(TEST_MODE) - timeoutInMinutes: 95 - strategy: - maxParallel: 4 - matrix: - 3.11 pip: - TEST_MODE: 'pip' - PYTHON_VERSION: '3.11' - 3.13 pip pre: - TEST_MODE: 'pip-pre' - PYTHON_VERSION: '3.13' - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: $(PYTHON_VERSION) - architecture: 'x64' - addToPath: true - - bash: | - set -xeo pipefail - git clone --depth 1 https://github.com/pyvista/setup-headless-display-action.git - MESA3D_VERSION=24.3.0 bash setup-headless-display-action/windows/install_opengl.sh - displayName: Install OpenGL - - bash: ./tools/azure_dependencies.sh - - script: pip install --only-binary=":all:" -e . - - script: mne sys_info -pd - - script: python -c "import numpy; numpy.show_config()" - - script: python -c "import numpy; import scipy.linalg; import sklearn.neighbors; from threadpoolctl import threadpool_info; from pprint import pprint; pprint(threadpool_info())" - displayName: Print threadpoolctl info - - bash: ./tools/get_testing_version.sh - - task: Cache@2 - inputs: - key: $(testing_version) - path: C:\Users\VssAdministrator\mne_data - displayName: 'Cache testing data' - - bash: ./tools/github_actions_download.sh - - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'Run tests' - - bash: bash <(curl -s https://codecov.io/bash) - condition: succeededOrFailed() diff --git a/mne/viz/backends/_utils.py b/mne/viz/backends/_utils.py index a22081879b1..b58d940439a 100644 --- a/mne/viz/backends/_utils.py +++ b/mne/viz/backends/_utils.py @@ -399,11 +399,13 @@ def func(self, *args, **kwargs): continue parent = self name = attr.split(".")[-1] - assert name try: for n in attr.split(".")[:-1]: parent = getattr(parent, n) - widget = getattr(parent, name, False) + if name: + widget = getattr(parent, name, False) + else: # empty string means "self" + widget = parent if widget: widget.close() del widget From a4bf43fa42ffc2ae111d44e3ab611b4441b37443 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:20:27 -0400 Subject: [PATCH 15/24] FIX: Flake [skip circle] --- mne/conftest.py | 9 +++------ mne/viz/backends/_utils.py | 8 +++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mne/conftest.py b/mne/conftest.py index c5ca124a43f..2ef937c7bd8 100644 --- a/mne/conftest.py +++ b/mne/conftest.py @@ -95,7 +95,6 @@ def pytest_configure(config: pytest.Config): "ultraslowtest: mark a test as ultraslow or to be run rarely", "pgtest: mark a test as relevant for mne-qt-browser", "pvtest: mark a test as relevant for pyvistaqt", - "allow_unclosed: allow unclosed pyvistaqt instances", ): config.addinivalue_line("markers", marker) @@ -1258,18 +1257,16 @@ def nirx_snirf(request): @pytest.fixture -def qt_windows_closed(request, qapp, qtbot): +def qt_windows_closed(request, qapp): """Ensure that no new Qt windows are open after a test.""" _check_skip_backend("pyvistaqt") qapp.processEvents() gc.collect() n_before = len(qapp.topLevelWidgets()) - marks = set(mark.name for mark in request.node.iter_markers()) yield - qapp.processEvents() + for _ in range(2): + qapp.processEvents() gc.collect() - if "allow_unclosed" in marks: - return # Don't check when the test fails if not _test_passed(request): return diff --git a/mne/viz/backends/_utils.py b/mne/viz/backends/_utils.py index b58d940439a..2b7b71799a0 100644 --- a/mne/viz/backends/_utils.py +++ b/mne/viz/backends/_utils.py @@ -388,6 +388,8 @@ def dec(meth, splash=splash, always_close=always_close): def func(self, *args, **kwargs): close_splash = always_close error = False + if not self: + return try: meth(self, *args, **kwargs) except Exception: @@ -402,7 +404,9 @@ def func(self, *args, **kwargs): try: for n in attr.split(".")[:-1]: parent = getattr(parent, n) - if name: + if not parent: + break + if parent and name: widget = getattr(parent, name, False) else: # empty string means "self" widget = parent @@ -416,6 +420,8 @@ def func(self, *args, **kwargs): delattr(parent, name) except Exception: pass + finally: + del parent, attr, do_close return func From 49e569b7467dc19b36d0399ddd1561a7ee6c5dac Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:40:11 -0400 Subject: [PATCH 16/24] FIX: Restore some stuff --- .github/workflows/tests.yml | 22 +++++- azure-pipelines.yml | 113 +++++++++++++++++++++++++++++++ mne/gui/tests/test_gui_api.py | 7 ++ tools/azure_dependencies.sh | 2 +- tools/github_actions_download.sh | 3 +- 5 files changed, 142 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c0d9ce930b7..b5a8386ccd5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,9 +67,27 @@ jobs: fail-fast: false matrix: include: + - os: ubuntu-latest + python: '3.13' + kind: pip - os: ubuntu-latest python: '3.14' kind: pip-pre + - os: ubuntu-latest + python: '3.13' + kind: conda + - os: macos-latest # arm64 + python: '3.14' + kind: pip + - os: macos-15-intel # Intel + python: '3.13' + kind: pip + - os: ubuntu-latest + python: '3.12' + kind: minimal + - os: ubuntu-22.04 + python: '3.10' + kind: old steps: - uses: actions/checkout@v6 with: @@ -139,7 +157,7 @@ jobs: - run: bash ./tools/check_qt_import.sh $MNE_QT_BACKEND if: env.MNE_QT_BACKEND != '' - name: Run tests with no testing data - run: MNE_SKIP_TESTING_DATASET_TESTS=true pytest -m "not (ultraslowtest or pgtest)" --tb=short --cov=mne --cov-report xml -vv -rfE mne/gui + run: MNE_SKIP_TESTING_DATASET_TESTS=true pytest -m "not (ultraslowtest or pgtest)" --tb=short --cov=mne --cov-report xml -vv -rfE mne/ if: matrix.kind == 'minimal' - run: ./tools/get_testing_version.sh - uses: actions/cache@v5 @@ -147,7 +165,7 @@ jobs: key: ${{ env.TESTING_VERSION }} path: ~/mne_data - run: bash ./tools/github_actions_download.sh - - run: pytest mne/gui # bash ./tools/github_actions_test.sh # for some reason on macOS we need to run "bash X" in order for a failed test run to show up + - run: bash ./tools/github_actions_test.sh # for some reason on macOS we need to run "bash X" in order for a failed test run to show up - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index eba6f807094..908b387f6e7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -53,14 +53,19 @@ stages: pre-commit install --install-hooks displayName: Install dependencies - bash: make pre-commit + displayName: 'make pre-commit' condition: always() - bash: make nesting + displayName: 'make nesting' condition: always() - bash: make check-readme + displayName: 'make check-readme' condition: always() - bash: mypy + displayName: 'mypy' condition: always() - bash: vulture + displayName: 'vulture' condition: always() - stage: Test @@ -69,6 +74,59 @@ stages: variables: AZURE_CI: 'true' jobs: + - job: Linux_Ultraslow_PG + pool: + vmImage: 'ubuntu-latest' + variables: + DISPLAY: ':99' + OPENBLAS_NUM_THREADS: '1' + OMP_NUM_THREADS: '1' + MNE_TEST_ALLOW_SKIP: '^$' # nothing + MNE_BROWSER_PRECOMPUTE: 'false' + steps: + - bash: | + set -xeo pipefail + ./tools/setup_xvfb.sh + sudo apt install -yq tcsh + displayName: 'Install Ubuntu dependencies' + - bash: ./tools/get_minimal_commands.sh + displayName: 'Get minimal commands' + - bash: | + echo $PATH + mne_surf2bem --version + fsl_rigid_register --version + displayName: 'Test minimal commands' + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.12' + architecture: 'x64' + addToPath: true + - bash: | + set -xeo pipefail + python -m pip install --progress-bar off --upgrade pip + python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef + displayName: 'Install dependencies with pip' + - bash: ./tools/check_qt_import.sh PySide6 + displayName: 'Check Qt import' + - bash: | + set -xeo pipefail + mne sys_info -pd + mne sys_info -pd | grep "qtpy .*(PySide6=.*)$" + displayName: Print config + - bash: ./tools/get_testing_version.sh + displayName: 'Get testing version' + - task: Cache@2 + inputs: + key: $(testing_version) + path: /home/vsts/mne_data + - bash: ./tools/github_actions_download.sh + displayName: 'Download testing data' + - script: pytest -m "ultraslowtest or pgtest" --tb=short --cov=mne --cov-report=xml -vv mne + displayName: 'slow and mne-qt-browser tests' + - bash: bash <(curl -s https://codecov.io/bash) + displayName: 'Codecov' + condition: succeededOrFailed() + - job: Linux_Qt_Bindings pool: vmImage: 'ubuntu-latest' @@ -97,17 +155,21 @@ stages: python ./tools/check_mne_location.py displayName: Print config - bash: ./tools/get_testing_version.sh + displayName: 'Get testing version' - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - bash: ./tools/github_actions_download.sh + displayName: 'Download testing data' - bash: ./tools/check_qt_import.sh PySide6 + displayName: 'Check Qt import' - bash: | set -xeo pipefail python -m pip install "PySide6!=6.8.0,!=6.8.0.1,!=6.9.1" mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PySide6=.*)$" + export MNE_TEST_ALLOW_SKIP="^.*PySide6 causes segfaults.*$" PYTEST_QT_API=PySide6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PySide6 displayName: 'PySide6' @@ -130,3 +192,54 @@ stages: - bash: bash <(curl -s https://codecov.io/bash) displayName: 'Codecov' condition: succeededOrFailed() + + - job: Windows + pool: + vmImage: 'windows-latest' + variables: + MNE_LOGGING_LEVEL: 'warning' + MNE_FORCE_SERIAL: 'true' + OPENBLAS_NUM_THREADS: '2' + OMP_DYNAMIC: 'false' + PYTHONUNBUFFERED: 1 + AZURE_CI_WINDOWS: 'true' + MNE_CI_KIND: $(TEST_MODE) + timeoutInMinutes: 95 + strategy: + maxParallel: 4 + matrix: + 3.11 pip: + TEST_MODE: 'pip' + PYTHON_VERSION: '3.11' + 3.13 pip pre: + TEST_MODE: 'pip-pre' + PYTHON_VERSION: '3.13' + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(PYTHON_VERSION) + architecture: 'x64' + addToPath: true + - bash: | + set -xeo pipefail + git clone --depth 1 https://github.com/pyvista/setup-headless-display-action.git + MESA3D_VERSION=24.3.0 bash setup-headless-display-action/windows/install_opengl.sh + displayName: Install OpenGL + - bash: ./tools/azure_dependencies.sh + - script: pip install --only-binary=":all:" -e . + - script: mne sys_info -pd + - script: python -c "import numpy; numpy.show_config()" + - script: python -c "import numpy; import scipy.linalg; import sklearn.neighbors; from threadpoolctl import threadpool_info; from pprint import pprint; pprint(threadpool_info())" + displayName: Print threadpoolctl info + - bash: ./tools/get_testing_version.sh + - task: Cache@2 + inputs: + key: $(testing_version) + path: C:\Users\VssAdministrator\mne_data + displayName: 'Cache testing data' + - bash: ./tools/github_actions_download.sh + - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne + displayName: 'Run tests' + - bash: bash <(curl -s https://codecov.io/bash) + displayName: 'Codecov' + condition: succeededOrFailed() diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 4e1e1708eba..35455bee0aa 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -2,6 +2,8 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import os + import pytest # These will skip all tests in this scope @@ -378,6 +380,11 @@ def _check_widget_trigger( del renderer +@pytest.mark.skipif() def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" + from qtpy import API_NAME as api + + if os.getenv("AZURE_CI") == "true" and api == "PySide6": + pytest.skip("PySide6 causes segfaults on Azure") test_gui_api_notebook(None, None, backend="qt") diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index 010992231f7..c7bf045086b 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -5,7 +5,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) STD_ARGS="--progress-bar off --upgrade --only-binary=:all:" python -m pip install $STD_ARGS pip setuptools wheel if [ "${TEST_MODE}" == "pip" ]; then - python -m pip install $STD_ARGS --only-binary="numba,llvmlite,numpy,scipy,vtk,dipy,openmeeg" -e .[full] --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" + python -m pip install $STD_ARGS --only-binary="numba,llvmlite,numpy,scipy,vtk,dipy,openmeeg" -e .[full-pyside6] --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" elif [ "${TEST_MODE}" == "pip-pre" ]; then ${SCRIPT_DIR}/install_pre_requirements.sh python -m pip install $STD_ARGS --pre -e . --group=test_extra diff --git a/tools/github_actions_download.sh b/tools/github_actions_download.sh index a503b562068..b67d430578e 100755 --- a/tools/github_actions_download.sh +++ b/tools/github_actions_download.sh @@ -1,8 +1,7 @@ #!/bin/bash -ef if [ "${MNE_CI_KIND}" != "minimal" ]; then - python -c 'import mne; mne.datasets.testing.data_path(verbose=True)'; - python -c "import mne; mne.datasets.misc.data_path(verbose=True)"; + python -uc "import mne; mne.datasets.testing.data_path(verbose=True)" # Make read-only to make sure we don't modify its contents TESTING_PATH=$(python -c "import mne; print(mne.datasets.testing.data_path(verbose=False))") echo "Testing data path: $TESTING_PATH" From eb035b60ca3a4768872eb822a2eab724f6d6ec21 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:40:45 -0400 Subject: [PATCH 17/24] FIX: Whoops --- mne/gui/tests/test_gui_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 35455bee0aa..cd2897169cc 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -380,7 +380,6 @@ def _check_widget_trigger( del renderer -@pytest.mark.skipif() def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" from qtpy import API_NAME as api From 1c269cf5212a7a004b16ab4bf83e03e4759ab32a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:43:36 -0400 Subject: [PATCH 18/24] FIX: Uniform --- azure-pipelines.yml | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 908b387f6e7..ed1e1ea134a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -44,7 +44,7 @@ stages: versionSpec: '3.14' architecture: 'x64' addToPath: true - displayName: 'Get Python' + displayName: Get Python - bash: | set -eo pipefail python -m pip install --progress-bar off --upgrade pip build @@ -53,19 +53,19 @@ stages: pre-commit install --install-hooks displayName: Install dependencies - bash: make pre-commit - displayName: 'make pre-commit' + displayName: make pre-commit condition: always() - bash: make nesting - displayName: 'make nesting' + displayName: make nesting condition: always() - bash: make check-readme - displayName: 'make check-readme' + displayName: make check-readme condition: always() - bash: mypy - displayName: 'mypy' + displayName: mypy condition: always() - bash: vulture - displayName: 'vulture' + displayName: vulture condition: always() - stage: Test @@ -88,14 +88,14 @@ stages: set -xeo pipefail ./tools/setup_xvfb.sh sudo apt install -yq tcsh - displayName: 'Install Ubuntu dependencies' + displayName: Install Ubuntu dependencies - bash: ./tools/get_minimal_commands.sh - displayName: 'Get minimal commands' + displayName: Get minimal commands - bash: | echo $PATH mne_surf2bem --version fsl_rigid_register --version - displayName: 'Test minimal commands' + displayName: Test minimal commands - task: UsePythonVersion@0 inputs: versionSpec: '3.12' @@ -105,26 +105,26 @@ stages: set -xeo pipefail python -m pip install --progress-bar off --upgrade pip python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e . --group=test "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader pymef - displayName: 'Install dependencies with pip' + displayName: Install dependencies with pip - bash: ./tools/check_qt_import.sh PySide6 - displayName: 'Check Qt import' + displayName: Check Qt import - bash: | set -xeo pipefail mne sys_info -pd mne sys_info -pd | grep "qtpy .*(PySide6=.*)$" displayName: Print config - bash: ./tools/get_testing_version.sh - displayName: 'Get testing version' + displayName: Get testing version - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - bash: ./tools/github_actions_download.sh - displayName: 'Download testing data' + displayName: Download testing data - script: pytest -m "ultraslowtest or pgtest" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'slow and mne-qt-browser tests' + displayName: slow and mne-qt-browser tests - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() - job: Linux_Qt_Bindings @@ -147,7 +147,7 @@ stages: python -m pip install --progress-bar off --upgrade pip python -m pip install --progress-bar off --upgrade --only-binary=":all:" -e .[full-pyside6] --group=test_extra "mne-qt-browser @ https://github.com/mne-tools/mne-qt-browser/archive/refs/heads/main.zip" python -c "import vtk" - displayName: 'Install dependencies with pip' + displayName: Install dependencies with pip - bash: | set -xeo pipefail which mne @@ -155,15 +155,15 @@ stages: python ./tools/check_mne_location.py displayName: Print config - bash: ./tools/get_testing_version.sh - displayName: 'Get testing version' + displayName: Get testing version - task: Cache@2 inputs: key: $(testing_version) path: /home/vsts/mne_data - bash: ./tools/github_actions_download.sh - displayName: 'Download testing data' + displayName: Download testing data - bash: ./tools/check_qt_import.sh PySide6 - displayName: 'Check Qt import' + displayName: Check Qt import - bash: | set -xeo pipefail python -m pip install "PySide6!=6.8.0,!=6.8.0.1,!=6.9.1" @@ -172,14 +172,14 @@ stages: export MNE_TEST_ALLOW_SKIP="^.*PySide6 causes segfaults.*$" PYTEST_QT_API=PySide6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PySide6 - displayName: 'PySide6' + displayName: PySide6 - bash: | set -xeo pipefail mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" PYTEST_QT_API=PyQt6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PyQt6 PyQt6-sip PyQt6-Qt6 - displayName: 'PyQt6' + displayName: PyQt6 # PyQt5 leaves cruft behind, so run it last - bash: | set -eo pipefail @@ -188,9 +188,9 @@ stages: mne sys_info -pd | grep "qtpy .* (PyQt5=.*)$" PYTEST_QT_API=PyQt5 pytest -m "not ultraslowtest" ${TEST_OPTIONS} python -m pip uninstall -yq PyQt5 PyQt5-sip PyQt5-Qt5 - displayName: 'PyQt5' + displayName: PyQt5 - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() - job: Windows @@ -236,10 +236,10 @@ stages: inputs: key: $(testing_version) path: C:\Users\VssAdministrator\mne_data - displayName: 'Cache testing data' + displayName: Cache testing data - bash: ./tools/github_actions_download.sh - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne - displayName: 'Run tests' + displayName: Run tests - bash: bash <(curl -s https://codecov.io/bash) - displayName: 'Codecov' + displayName: Codecov condition: succeededOrFailed() From 38bca5369d0bf963eae6adc5a22275e4a7e03a1b Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:49:40 -0400 Subject: [PATCH 19/24] FIX: Skips --- azure-pipelines.yml | 5 ++++- tools/azure_dependencies.sh | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ed1e1ea134a..e6a0f804285 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -134,7 +134,7 @@ stages: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' TEST_OPTIONS: "--tb=short --cov=mne --cov-report=xml --cov-append -vv mne/gui mne/viz/_brain mne/viz/backends mne/viz/tests/test_evoked.py mne/report" - MNE_TEST_ALLOW_SKIP: '^$' # nothing + MNE_TEST_ALLOW_SKIP: '^$' # nothing (can be overridden below) steps: - bash: ./tools/setup_xvfb.sh - task: UsePythonVersion@0 @@ -204,6 +204,7 @@ stages: PYTHONUNBUFFERED: 1 AZURE_CI_WINDOWS: 'true' MNE_CI_KIND: $(TEST_MODE) + MNE_TEST_ALLOW_SKIP: $(MNE_TEST_ALLOW_SKIP) timeoutInMinutes: 95 strategy: maxParallel: 4 @@ -211,9 +212,11 @@ stages: 3.11 pip: TEST_MODE: 'pip' PYTHON_VERSION: '3.11' + MNE_TEST_ALLOW_SKIP: "^.*(Requires MNE-C|CUDA not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*$" 3.13 pip pre: TEST_MODE: 'pip-pre' PYTHON_VERSION: '3.13' + MNE_TEST_ALLOW_SKIP: "^.*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL|PySide6 causes segfaults).*$" steps: - task: UsePythonVersion@0 inputs: diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index c7bf045086b..1569a6bac15 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -9,7 +9,6 @@ if [ "${TEST_MODE}" == "pip" ]; then elif [ "${TEST_MODE}" == "pip-pre" ]; then ${SCRIPT_DIR}/install_pre_requirements.sh python -m pip install $STD_ARGS --pre -e . --group=test_extra - echo "##vso[task.setvariable variable=MNE_TEST_ALLOW_SKIP].*(Requires (spm|brainstorm) dataset|Requires MNE-C|CUDA not|Numba not| on Windows|MNE_FORCE_SERIAL).*" else echo "Unknown run type ${TEST_MODE}" exit 1 From 7da7388afd9c353c719cf22ab194118a03a2a7ab Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:55:43 -0400 Subject: [PATCH 20/24] FIX: Name --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e6a0f804285..25c5df3245d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -137,6 +137,7 @@ stages: MNE_TEST_ALLOW_SKIP: '^$' # nothing (can be overridden below) steps: - bash: ./tools/setup_xvfb.sh + displayName: Setup up Xvfb - task: UsePythonVersion@0 inputs: versionSpec: '3.14' From fc44de94fb00723a5babf40acd7f40709af06fa8 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 19:59:55 -0400 Subject: [PATCH 21/24] FIX: Names --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 25c5df3245d..c52932d11b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -231,8 +231,11 @@ stages: displayName: Install OpenGL - bash: ./tools/azure_dependencies.sh - script: pip install --only-binary=":all:" -e . + displayName: Install mne - script: mne sys_info -pd + displayName: Print config - script: python -c "import numpy; numpy.show_config()" + displayName: Print NumPy config - script: python -c "import numpy; import scipy.linalg; import sklearn.neighbors; from threadpoolctl import threadpool_info; from pprint import pprint; pprint(threadpool_info())" displayName: Print threadpoolctl info - bash: ./tools/get_testing_version.sh @@ -242,6 +245,7 @@ stages: path: C:\Users\VssAdministrator\mne_data displayName: Cache testing data - bash: ./tools/github_actions_download.sh + displayName: Download testing data - script: pytest -m "not (slowtest or pgtest)" --tb=short --cov=mne --cov-report=xml -vv mne displayName: Run tests - bash: bash <(curl -s https://codecov.io/bash) From 8839d8dade1134191e4215f568db49f179433a1d Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 23:15:45 -0400 Subject: [PATCH 22/24] FIX: Try again --- azure-pipelines.yml | 2 +- mne/gui/tests/test_gui_api.py | 6 ++++-- tools/github_actions_env_vars.sh | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c52932d11b3..7e273605ff9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -167,7 +167,6 @@ stages: displayName: Check Qt import - bash: | set -xeo pipefail - python -m pip install "PySide6!=6.8.0,!=6.8.0.1,!=6.9.1" mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PySide6=.*)$" export MNE_TEST_ALLOW_SKIP="^.*PySide6 causes segfaults.*$" @@ -176,6 +175,7 @@ stages: displayName: PySide6 - bash: | set -xeo pipefail + python -m pip install PyQt6 mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" PYTEST_QT_API=PyQt6 pytest -m "not ultraslowtest" ${TEST_OPTIONS} diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index cd2897169cc..f43f85251f6 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -384,6 +384,8 @@ def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" from qtpy import API_NAME as api - if os.getenv("AZURE_CI") == "true" and api == "PySide6": - pytest.skip("PySide6 causes segfaults on Azure") + if ( + os.getenv("AZURE_CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true" + ) and api == "PySide6": + pytest.skip("PySide6 causes intermittent segfaults on CIs") test_gui_api_notebook(None, None, backend="qt") diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index 306eb320092..f8ed3320e2c 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -8,7 +8,7 @@ if [[ "$MNE_CI_KIND" == "pip"* ]] || [[ "$MNE_CI_KIND" == "minimal" ]]; then # We should test an eager import somewhere, might as well be here echo "EAGER_IMPORT=true" | tee -a $GITHUB_ENV # Make sure nothing unexpected is skipped - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Numba not).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(CUDA not|Numba not|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV fi echo "MNE_QT_BACKEND=PySide6" | tee -a $GITHUB_ENV elif [[ "$MNE_CI_KIND" == "old" ]]; then @@ -19,7 +19,7 @@ elif [[ "$MNE_CI_KIND" == "conda" ]]; then echo "Setting conda env vars for $MNE_CI_KIND" echo "CONDA_ENV=environment.yml" | tee -a $GITHUB_ENV echo "MNE_LOGGING_LEVEL=warning" | tee -a $GITHUB_ENV - echo "MNE_TEST_ALLOW_SKIP=.*(Requires (spm|brainstorm) dataset|CUDA not|Accelerate|Flakey verbose behavior).*" | tee -a $GITHUB_ENV + echo "MNE_TEST_ALLOW_SKIP=.*(CUDA not|Accelerate|Flakey verbose behavior|PySide6 causes segfaults).*" | tee -a $GITHUB_ENV # Our cache_dir test has problems when the path is too long, so prevent it from getting too long if [[ "$CI_OS_NAME" == "macos"* ]]; then echo "PYTEST_DEBUG_TEMPROOT=/tmp" | tee -a $GITHUB_ENV From c58a714997819b4eb4302db3ed5565e4af9d1595 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 23:17:26 -0400 Subject: [PATCH 23/24] FIX: Restor --- mne/gui/tests/test_gui_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index f43f85251f6..71d4a2c3465 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -6,6 +6,8 @@ import pytest +from mne.utils import _check_qt_version + # These will skip all tests in this scope pytest.importorskip("nibabel") @@ -382,8 +384,10 @@ def _check_widget_trigger( def test_gui_api_qt(renderer_interactive_pyvistaqt): """Test GUI API with the Qt backend.""" - from qtpy import API_NAME as api - + _, api = _check_qt_version(return_api=True) + # TODO: After merging https://github.com/mne-tools/mne-python/pull/11567 + # The Qt CI run started failing about 50% of the time, so let's skip this + # for now. if ( os.getenv("AZURE_CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true" ) and api == "PySide6": From 76fd0b5a2d0c8abfe76579083e9158c78584e79c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 18 May 2026 23:18:34 -0400 Subject: [PATCH 24/24] FIX : Msg --- mne/gui/tests/test_gui_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 71d4a2c3465..fdac40839f5 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -391,5 +391,5 @@ def test_gui_api_qt(renderer_interactive_pyvistaqt): if ( os.getenv("AZURE_CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true" ) and api == "PySide6": - pytest.skip("PySide6 causes intermittent segfaults on CIs") + pytest.skip("PySide6 causes segfaults on CIs sometimes") test_gui_api_notebook(None, None, backend="qt")