Repository: mne-tools/mne-python

Issue:
MAINT: pip-pre failures
All PRs are failing pip-pre with for example:

https://github.com/mne-tools/mne-python/actions/runs/7562092509/job/20668202667?pr=12237

Looks like there is a lot of stuff to fix here including some invalid value and linalg stuff. :(

I'll try to look Monday


Discussion:

Resolving patch:
diff --git a/mne/conftest.py b/mne/conftest.py
--- a/mne/conftest.py
+++ b/mne/conftest.py
@@ -32,6 +32,7 @@
     _assert_no_instances,
     _check_qt_version,
     _pl,
+    _record_warnings,
     _TempDir,
     numerics,
 )
@@ -801,7 +802,9 @@ def src_volume_labels():
     """Create a 7mm source space with labels."""
     pytest.importorskip("nibabel")
     volume_labels = mne.get_volume_labels_from_aseg(fname_aseg)
-    with pytest.warns(RuntimeWarning, match="Found no usable.*t-vessel.*"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="Found no usable.*t-vessel.*"
+    ):
         src = mne.setup_volume_source_space(
             "sample",
             7.0,
diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py
--- a/mne/io/brainvision/brainvision.py
+++ b/mne/io/brainvision/brainvision.py
@@ -344,9 +344,10 @@ def _read_annotations_brainvision(fname, sfreq="auto"):
 
 def _check_bv_version(header, kind):
     """Check the header version."""
-    _data_err = """\
-    MNE-Python currently only supports %s versions 1.0 and 2.0, got unparsable\
-     %r. Contact MNE-Python developers for support."""
+    _data_err = (
+        "MNE-Python currently only supports %s versions 1.0 and 2.0, got unparsable "
+        "%r. Contact MNE-Python developers for support."
+    )
     # optional space, optional Core or V-Amp, optional Exchange,
     # Version/Header, optional comma, 1/2
     _data_re = (
@@ -355,14 +356,15 @@ def _check_bv_version(header, kind):
 
     assert kind in ("header", "marker")
 
-    if header == "":
-        warn(f"Missing header in {kind} file.")
     for version in range(1, 3):
         this_re = _data_re % (kind.capitalize(), version)
         if re.search(this_re, header) is not None:
             return version
     else:
-        warn(_data_err % (kind, header))
+        if header == "":
+            warn(f"Missing header in {kind} file.")
+        else:
+            warn(_data_err % (kind, header))
 
 
 _orientation_dict = dict(MULTIPLEXED="F", VECTORIZED="C")


Test patch:
diff --git a/mne/_fiff/tests/test_meas_info.py b/mne/_fiff/tests/test_meas_info.py
--- a/mne/_fiff/tests/test_meas_info.py
+++ b/mne/_fiff/tests/test_meas_info.py
@@ -350,9 +350,10 @@ def test_read_write_info(tmp_path):
 @testing.requires_testing_data
 def test_dir_warning():
     """Test that trying to read a bad filename emits a warning before an error."""
-    with pytest.raises(OSError, match="directory"):
-        with pytest.warns(RuntimeWarning, match="foo"):
-            read_info(ctf_fname)
+    with pytest.raises(OSError, match="directory"), pytest.warns(
+        RuntimeWarning, match="does not conform"
+    ):
+        read_info(ctf_fname)
 
 
 def test_io_dig_points(tmp_path):
diff --git a/mne/beamformer/tests/test_lcmv.py b/mne/beamformer/tests/test_lcmv.py
--- a/mne/beamformer/tests/test_lcmv.py
+++ b/mne/beamformer/tests/test_lcmv.py
@@ -589,19 +589,20 @@ def test_make_lcmv_sphere(pick_ori, weight_norm):
     fwd_sphere = mne.make_forward_solution(evoked.info, None, src, sphere)
 
     # Test that we get an error if not reducing rank
-    with pytest.raises(ValueError, match="Singular matrix detected"):
-        with pytest.warns(RuntimeWarning, match="positive semidefinite"):
-            make_lcmv(
-                evoked.info,
-                fwd_sphere,
-                data_cov,
-                reg=0.1,
-                noise_cov=noise_cov,
-                weight_norm=weight_norm,
-                pick_ori=pick_ori,
-                reduce_rank=False,
-                rank="full",
-            )
+    with pytest.raises(
+        ValueError, match="Singular matrix detected"
+    ), _record_warnings(), pytest.warns(RuntimeWarning, match="positive semidefinite"):
+        make_lcmv(
+            evoked.info,
+            fwd_sphere,
+            data_cov,
+            reg=0.1,
+            noise_cov=noise_cov,
+            weight_norm=weight_norm,
+            pick_ori=pick_ori,
+            reduce_rank=False,
+            rank="full",
+        )
 
     # Now let's reduce it
     filters = make_lcmv(
diff --git a/mne/export/tests/test_export.py b/mne/export/tests/test_export.py
--- a/mne/export/tests/test_export.py
+++ b/mne/export/tests/test_export.py
@@ -78,7 +78,9 @@ def test_export_raw_pybv(tmp_path, meas_date, orig_time, ext):
     raw.set_annotations(annots)
 
     temp_fname = tmp_path / ("test" + ext)
-    with pytest.warns(RuntimeWarning, match="'short' format. Converting"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="'short' format. Converting"
+    ):
         raw.export(temp_fname)
     raw_read = read_raw_brainvision(str(temp_fname).replace(".eeg", ".vhdr"))
     assert raw.ch_names == raw_read.ch_names
@@ -301,7 +303,9 @@ def test_export_edf_signal_clipping(tmp_path, physical_range, exceeded_bound):
     raw = read_raw_fif(fname_raw)
     raw.pick(picks=["eeg", "ecog", "seeg"]).load_data()
     temp_fname = tmp_path / "test.edf"
-    with pytest.warns(RuntimeWarning, match=f"The {exceeded_bound}"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match=f"The {exceeded_bound}"
+    ):
         raw.export(temp_fname, physical_range=physical_range)
     raw_read = read_raw_edf(temp_fname, preload=True)
     assert raw_read.get_data().min() >= physical_range[0]
diff --git a/mne/forward/tests/test_forward.py b/mne/forward/tests/test_forward.py
--- a/mne/forward/tests/test_forward.py
+++ b/mne/forward/tests/test_forward.py
@@ -37,7 +37,7 @@
 )
 from mne.io import read_info
 from mne.label import read_label
-from mne.utils import requires_mne, run_subprocess
+from mne.utils import _record_warnings, requires_mne, run_subprocess
 
 data_path = testing.data_path(download=False)
 fname_meeg = data_path / "MEG" / "sample" / "sample_audvis_trunc-meg-eeg-oct-4-fwd.fif"
@@ -230,7 +230,9 @@ def test_apply_forward():
     # Evoked
     evoked = read_evokeds(fname_evoked, condition=0)
     evoked.pick(picks="meg")
-    with pytest.warns(RuntimeWarning, match="only .* positive values"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="only .* positive values"
+    ):
         evoked = apply_forward(fwd, stc, evoked.info, start=start, stop=stop)
     data = evoked.data
     times = evoked.times
@@ -248,13 +250,14 @@ def test_apply_forward():
         stc.tmin,
         stc.tstep,
     )
-    with pytest.warns(RuntimeWarning, match="very large"):
+    large_ctx = pytest.warns(RuntimeWarning, match="very large")
+    with large_ctx:
         evoked_2 = apply_forward(fwd, stc_vec, evoked.info)
     assert np.abs(evoked_2.data).mean() > 1e-5
     assert_allclose(evoked.data, evoked_2.data, atol=1e-10)
 
     # Raw
-    with pytest.warns(RuntimeWarning, match="only .* positive values"):
+    with large_ctx, pytest.warns(RuntimeWarning, match="only .* positive values"):
         raw_proj = apply_forward_raw(fwd, stc, evoked.info, start=start, stop=stop)
     data, times = raw_proj[:, :]
 
diff --git a/mne/forward/tests/test_make_forward.py b/mne/forward/tests/test_make_forward.py
--- a/mne/forward/tests/test_make_forward.py
+++ b/mne/forward/tests/test_make_forward.py
@@ -44,6 +44,7 @@
 from mne.surface import _get_ico_surface
 from mne.transforms import Transform
 from mne.utils import (
+    _record_warnings,
     catch_logging,
     requires_mne,
     requires_mne_mark,
@@ -198,7 +199,7 @@ def test_magnetic_dipole():
     r0 = coils[0]["rmag"][[0]]
     with pytest.raises(RuntimeError, match="Coil too close"):
         _magnetic_dipole_field_vec(r0, coils[:1])
-    with pytest.warns(RuntimeWarning, match="Coil too close"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Coil too close"):
         fwd = _magnetic_dipole_field_vec(r0, coils[:1], too_close="warning")
     assert not np.isfinite(fwd).any()
     with np.errstate(invalid="ignore"):
diff --git a/mne/io/artemis123/tests/test_artemis123.py b/mne/io/artemis123/tests/test_artemis123.py
--- a/mne/io/artemis123/tests/test_artemis123.py
+++ b/mne/io/artemis123/tests/test_artemis123.py
@@ -97,7 +97,9 @@ def test_dev_head_t():
     assert_equal(raw.info["sfreq"], 5000.0)
 
     # test with head loc and digitization
-    with pytest.warns(RuntimeWarning, match="Large difference"):
+    with pytest.warns(RuntimeWarning, match="consistency"), pytest.warns(
+        RuntimeWarning, match="Large difference"
+    ):
         raw = read_raw_artemis123(
             short_HPI_dip_fname, add_head_trans=True, pos_fname=dig_fname
         )
diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py
--- a/mne/io/brainvision/tests/test_brainvision.py
+++ b/mne/io/brainvision/tests/test_brainvision.py
@@ -20,7 +20,7 @@
 from mne.datasets import testing
 from mne.io import read_raw_brainvision, read_raw_fif
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.utils import _stamp_to_dt, object_diff
+from mne.utils import _record_warnings, _stamp_to_dt, object_diff
 
 data_dir = Path(__file__).parent / "data"
 vhdr_path = data_dir / "test.vhdr"
@@ -72,6 +72,8 @@
 #      This should be amend in its own PR.
 montage = data_dir / "test.hpts"
 
+_no_dig = pytest.warns(RuntimeWarning, match="No info on DataPoints")
+
 
 def test_orig_units(recwarn):
     """Test exposure of original channel units."""
@@ -473,7 +475,7 @@ def test_brainvision_data_partially_disabled_hw_filters():
 
 def test_brainvision_data_software_filters_latin1_global_units():
     """Test reading raw Brain Vision files."""
-    with pytest.warns(RuntimeWarning, match="software filter"):
+    with _no_dig, pytest.warns(RuntimeWarning, match="software filter"):
         raw = _test_raw_reader(
             read_raw_brainvision,
             vhdr_fname=vhdr_old_path,
@@ -485,7 +487,7 @@ def test_brainvision_data_software_filters_latin1_global_units():
     assert raw.info["lowpass"] == 50.0
 
     # test sensor name with spaces (#9299)
-    with pytest.warns(RuntimeWarning, match="software filter"):
+    with _no_dig, pytest.warns(RuntimeWarning, match="software filter"):
         raw = _test_raw_reader(
             read_raw_brainvision,
             vhdr_fname=vhdr_old_longname_path,
@@ -566,7 +568,7 @@ def test_brainvision_data():
 
 def test_brainvision_vectorized_data():
     """Test reading BrainVision data files with vectorized data."""
-    with pytest.warns(RuntimeWarning, match="software filter"):
+    with _no_dig, pytest.warns(RuntimeWarning, match="software filter"):
         raw = read_raw_brainvision(vhdr_old_path, preload=True)
 
     assert_array_equal(raw._data.shape, (29, 251))
@@ -611,7 +613,9 @@ def test_brainvision_vectorized_data():
 def test_coodinates_extraction():
     """Test reading of [Coordinates] section if present."""
     # vhdr 2 has a Coordinates section
-    with pytest.warns(RuntimeWarning, match="coordinate information"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="coordinate information"
+    ):
         raw = read_raw_brainvision(vhdr_v2_path)
 
     # Basic check of extracted coordinates
diff --git a/mne/io/cnt/tests/test_cnt.py b/mne/io/cnt/tests/test_cnt.py
--- a/mne/io/cnt/tests/test_cnt.py
+++ b/mne/io/cnt/tests/test_cnt.py
@@ -19,10 +19,13 @@
 fname_bad_spans = data_path / "CNT" / "test_CNT_events_mne_JWoess_clipped.cnt"
 
 
+_no_parse = pytest.warns(RuntimeWarning, match="Could not parse")
+
+
 @testing.requires_testing_data
 def test_old_data():
     """Test reading raw cnt files."""
-    with pytest.warns(RuntimeWarning, match="number of bytes"):
+    with _no_parse, pytest.warns(RuntimeWarning, match="number of bytes"):
         raw = _test_raw_reader(
             read_raw_cnt, input_fname=fname, eog="auto", misc=["NA1", "LEFT_EAR"]
         )
@@ -50,12 +53,12 @@ def test_new_data():
 @testing.requires_testing_data
 def test_auto_data():
     """Test reading raw cnt files with automatic header."""
-    with pytest.warns(RuntimeWarning):
+    with pytest.warns(RuntimeWarning, match="Omitted 6 annot"):
         raw = read_raw_cnt(input_fname=fname_bad_spans)
 
     assert raw.info["bads"] == ["F8"]
 
-    with pytest.warns(RuntimeWarning, match="number of bytes"):
+    with _no_parse, pytest.warns(RuntimeWarning, match="number of bytes"):
         raw = _test_raw_reader(
             read_raw_cnt, input_fname=fname, eog="auto", misc=["NA1", "LEFT_EAR"]
         )
@@ -74,7 +77,7 @@ def test_auto_data():
 @testing.requires_testing_data
 def test_compare_events_and_annotations():
     """Test comparing annotations and events."""
-    with pytest.warns(RuntimeWarning, match="Could not parse meas date"):
+    with _no_parse, pytest.warns(RuntimeWarning, match="Could not define the num"):
         raw = read_raw_cnt(fname)
     events = np.array(
         [[333, 0, 7], [1010, 0, 7], [1664, 0, 109], [2324, 0, 7], [2984, 0, 109]]
diff --git a/mne/io/ctf/tests/test_ctf.py b/mne/io/ctf/tests/test_ctf.py
--- a/mne/io/ctf/tests/test_ctf.py
+++ b/mne/io/ctf/tests/test_ctf.py
@@ -113,7 +113,7 @@ def test_read_ctf(tmp_path):
     shutil.copytree(ctf_eeg_fname, ctf_no_hc_fname)
     remove_base = op.join(ctf_no_hc_fname, op.basename(ctf_fname_catch[:-3]))
     os.remove(remove_base + ".hc")
-    with pytest.warns(RuntimeWarning, match="MISC channel"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="MISC channel"):
         pytest.raises(RuntimeError, read_raw_ctf, ctf_no_hc_fname)
     os.remove(remove_base + ".eeg")
     shutil.copy(
diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py
--- a/mne/io/edf/tests/test_edf.py
+++ b/mne/io/edf/tests/test_edf.py
@@ -38,6 +38,7 @@
 )
 from mne.io.tests.test_raw import _test_raw_reader
 from mne.tests.test_annotations import _assert_annotations_equal
+from mne.utils import _record_warnings
 
 td_mark = testing._pytest_mark()
 
@@ -408,7 +409,7 @@ def test_no_data_channels():
     annot_2 = raw.annotations
     _assert_annotations_equal(annot, annot_2)
     # only annotations (should warn)
-    with pytest.warns(RuntimeWarning, match="read_annotations"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="read_annotations"):
         read_raw_edf(edf_annot_only)
 
 
diff --git a/mne/io/edf/tests/test_gdf.py b/mne/io/edf/tests/test_gdf.py
--- a/mne/io/edf/tests/test_gdf.py
+++ b/mne/io/edf/tests/test_gdf.py
@@ -153,7 +153,7 @@ def test_gdf2_data():
 @testing.requires_testing_data
 def test_one_channel_gdf():
     """Test a one-channel GDF file."""
-    with pytest.warns(RuntimeWarning, match="different highpass"):
+    with pytest.warns(RuntimeWarning, match="contain different"):
         ecg = read_raw_gdf(gdf_1ch_path, preload=True)
     assert ecg["ECG"][0].shape == (1, 4500)
     assert 150.0 == ecg.info["sfreq"]
diff --git a/mne/io/eeglab/tests/test_eeglab.py b/mne/io/eeglab/tests/test_eeglab.py
--- a/mne/io/eeglab/tests/test_eeglab.py
+++ b/mne/io/eeglab/tests/test_eeglab.py
@@ -28,7 +28,7 @@
 from mne.io.eeglab._eeglab import _readmat
 from mne.io.eeglab.eeglab import _dol_to_lod, _get_montage_information
 from mne.io.tests.test_raw import _test_raw_reader
-from mne.utils import Bunch, _check_pymatreader_installed
+from mne.utils import Bunch, _check_pymatreader_installed, _record_warnings
 
 base_dir = testing.data_path(download=False) / "EEGLAB"
 raw_fname_mat = base_dir / "test_raw.set"
@@ -140,7 +140,9 @@ def test_io_set_raw_more(tmp_path):
     shutil.copyfile(
         base_dir / "test_raw.fdt", negative_latency_fname.with_suffix(".fdt")
     )
-    with pytest.warns(RuntimeWarning, match="has a sample index of -1."):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="has a sample index of -1."
+    ):
         read_raw_eeglab(input_fname=negative_latency_fname, preload=True)
 
     # test negative event latencies
@@ -163,7 +165,7 @@ def test_io_set_raw_more(tmp_path):
         oned_as="row",
     )
     with pytest.raises(ValueError, match="event sample index is negative"):
-        with pytest.warns(RuntimeWarning, match="has a sample index of -1."):
+        with _record_warnings():
             read_raw_eeglab(input_fname=negative_latency_fname, preload=True)
 
     # test overlapping events
@@ -350,9 +352,9 @@ def test_io_set_raw_more(tmp_path):
 def test_io_set_epochs(fnames):
     """Test importing EEGLAB .set epochs files."""
     epochs_fname, epochs_fname_onefile = fnames
-    with pytest.warns(RuntimeWarning, match="multiple events"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="multiple events"):
         epochs = read_epochs_eeglab(epochs_fname)
-    with pytest.warns(RuntimeWarning, match="multiple events"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="multiple events"):
         epochs2 = read_epochs_eeglab(epochs_fname_onefile)
     # one warning for each read_epochs_eeglab because both files have epochs
     # associated with multiple events
diff --git a/mne/io/eyelink/tests/test_eyelink.py b/mne/io/eyelink/tests/test_eyelink.py
--- a/mne/io/eyelink/tests/test_eyelink.py
+++ b/mne/io/eyelink/tests/test_eyelink.py
@@ -12,6 +12,7 @@
 from mne.io import read_raw_eyelink
 from mne.io.eyelink._utils import _adjust_times, _find_overlaps
 from mne.io.tests.test_raw import _test_raw_reader
+from mne.utils import _record_warnings
 
 pd = pytest.importorskip("pandas")
 
@@ -255,7 +256,9 @@ def test_multi_block_misc_channels(fname, tmp_path):
     out_file = tmp_path / "tmp_eyelink.asc"
     _simulate_eye_tracking_data(fname, out_file)
 
-    with pytest.warns(RuntimeWarning, match="Raw eyegaze coordinates"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="Raw eyegaze coordinates"
+    ):
         raw = read_raw_eyelink(out_file, apply_offsets=True)
 
     chs_in_file = [
diff --git a/mne/io/fieldtrip/tests/test_fieldtrip.py b/mne/io/fieldtrip/tests/test_fieldtrip.py
--- a/mne/io/fieldtrip/tests/test_fieldtrip.py
+++ b/mne/io/fieldtrip/tests/test_fieldtrip.py
@@ -68,16 +68,14 @@ def test_read_evoked(cur_system, version, use_info):
     """Test comparing reading an Evoked object and the FieldTrip version."""
     test_data_folder_ft = get_data_paths(cur_system)
     mne_avg = get_evoked(cur_system)
+    cur_fname = test_data_folder_ft / f"averaged_{version}.mat"
     if use_info:
         info = get_raw_info(cur_system)
-        ctx = nullcontext()
+        avg_ft = mne.io.read_evoked_fieldtrip(cur_fname, info)
     else:
         info = None
-        ctx = pytest.warns(**no_info_warning)
-
-    cur_fname = test_data_folder_ft / f"averaged_{version}.mat"
-    with ctx:
-        avg_ft = mne.io.read_evoked_fieldtrip(cur_fname, info)
+        with _record_warnings(), pytest.warns(**no_info_warning):
+            avg_ft = mne.io.read_evoked_fieldtrip(cur_fname, info)
 
     mne_data = mne_avg.data[:, :-1]
     ft_data = avg_ft.data
@@ -98,6 +96,7 @@ def test_read_epochs(cur_system, version, use_info, monkeypatch):
     has_pandas = pandas is not False
     test_data_folder_ft = get_data_paths(cur_system)
     mne_epoched = get_epochs(cur_system)
+    cur_fname = test_data_folder_ft / f"epoched_{version}.mat"
     if use_info:
         info = get_raw_info(cur_system)
         ctx = nullcontext()
@@ -105,9 +104,8 @@ def test_read_epochs(cur_system, version, use_info, monkeypatch):
         info = None
         ctx = pytest.warns(**no_info_warning)
 
-    cur_fname = test_data_folder_ft / f"epoched_{version}.mat"
     if has_pandas:
-        with ctx:
+        with _record_warnings(), ctx:
             epoched_ft = mne.io.read_epochs_fieldtrip(cur_fname, info)
         assert isinstance(epoched_ft.metadata, pandas.DataFrame)
     else:
@@ -133,7 +131,7 @@ def modify_mat(fname, variable_names=None, ignore_fields=None):
         return out
 
     monkeypatch.setattr(pymatreader, "read_mat", modify_mat)
-    with pytest.warns(RuntimeWarning, match="multiple"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="multiple"):
         mne.io.read_epochs_fieldtrip(cur_fname, info)
 
 
@@ -160,7 +158,7 @@ def test_read_raw_fieldtrip(cur_system, version, use_info):
 
     cur_fname = test_data_folder_ft / f"raw_{version}.mat"
 
-    with ctx:
+    with _record_warnings(), ctx:
         raw_fiff_ft = mne.io.read_raw_fieldtrip(cur_fname, info)
 
     if cur_system == "BTI" and not use_info:
diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py
--- a/mne/io/fiff/tests/test_raw_fiff.py
+++ b/mne/io/fiff/tests/test_raw_fiff.py
@@ -7,7 +7,6 @@
 import os
 import pathlib
 import pickle
-import platform
 import shutil
 import sys
 from copy import deepcopy
@@ -43,7 +42,6 @@
     assert_and_remove_boundary_annot,
     assert_object_equal,
     catch_logging,
-    check_version,
     requires_mne,
     run_subprocess,
 )
@@ -903,7 +901,7 @@ def test_io_complex(tmp_path, dtype):
 @testing.requires_testing_data
 def test_getitem():
     """Test getitem/indexing of Raw."""
-    for preload in [False, True, "memmap.dat"]:
+    for preload in [False, True, "memmap1.dat"]:
         raw = read_raw_fif(fif_fname, preload=preload)
         data, times = raw[0, :]
         data1, times1 = raw[0]
@@ -1022,11 +1020,9 @@ def test_proj(tmp_path):
 
 
 @testing.requires_testing_data
-@pytest.mark.parametrize("preload", [False, True, "memmap.dat"])
+@pytest.mark.parametrize("preload", [False, True, "memmap2.dat"])
 def test_preload_modify(preload, tmp_path):
     """Test preloading and modifying data."""
-    if platform.system() == "Windows" and check_version("numpy", "2.0.0dev"):
-        pytest.skip("Problem on Windows, see numpy/issues/25665")
     rng = np.random.RandomState(0)
     raw = read_raw_fif(fif_fname, preload=preload)
 
@@ -1930,9 +1926,7 @@ def test_equalize_channels():
 def test_memmap(tmp_path):
     """Test some interesting memmapping cases."""
     # concatenate_raw
-    if platform.system() == "Windows" and check_version("numpy", "2.0.0dev"):
-        pytest.skip("Problem on Windows, see numpy/issues/25665")
-    memmaps = [str(tmp_path / str(ii)) for ii in range(3)]
+    memmaps = [str(tmp_path / str(ii)) for ii in range(4)]
     raw_0 = read_raw_fif(test_fif_fname, preload=memmaps[0])
     assert raw_0._data.filename == memmaps[0]
     raw_1 = read_raw_fif(test_fif_fname, preload=memmaps[1])
@@ -1957,8 +1951,8 @@ def test_memmap(tmp_path):
     # now let's see if .copy() actually works; it does, but eventually
     # we should make it optionally memmap to a new filename rather than
     # create an in-memory version (filename=None)
-    raw_0 = read_raw_fif(test_fif_fname, preload=memmaps[0])
-    assert raw_0._data.filename == memmaps[0]
+    raw_0 = read_raw_fif(test_fif_fname, preload=memmaps[3])
+    assert raw_0._data.filename == memmaps[3]
     assert raw_0._data[:1, 3:5].all()
     raw_1 = raw_0.copy()
     assert isinstance(raw_1._data, np.memmap)
@@ -2098,7 +2092,9 @@ def test_corrupted(tmp_path, offset):
     bad_fname = tmp_path / "test_raw.fif"
     with open(bad_fname, "wb") as fid:
         fid.write(data)
-    with pytest.warns(RuntimeWarning, match=".*tag directory.*corrupt.*"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match=".*tag directory.*corrupt.*"
+    ):
         raw_bad = read_raw_fif(bad_fname)
     assert_allclose(raw.get_data(), raw_bad.get_data())
 
diff --git a/mne/preprocessing/tests/test_ica.py b/mne/preprocessing/tests/test_ica.py
--- a/mne/preprocessing/tests/test_ica.py
+++ b/mne/preprocessing/tests/test_ica.py
@@ -78,6 +78,8 @@
 )
 pytest.importorskip("sklearn")
 
+_baseline_corrected = pytest.warns(RuntimeWarning, match="were baseline-corrected")
+
 
 def ICA(*args, **kwargs):
     """Fix the random state in tests."""
@@ -171,7 +173,9 @@ def test_ica_simple(method):
     info = create_info(data.shape[-2], 1000.0, "eeg")
     cov = make_ad_hoc_cov(info)
     ica = ICA(n_components=n_components, method=method, random_state=0, noise_cov=cov)
-    with pytest.warns(RuntimeWarning, match="No average EEG.*"):
+    with pytest.warns(RuntimeWarning, match="high-pass filtered"), pytest.warns(
+        RuntimeWarning, match="No average EEG.*"
+    ):
         ica.fit(RawArray(data, info))
     transform = ica.unmixing_matrix_ @ ica.pca_components_ @ A
     amari_distance = np.mean(
@@ -649,7 +653,7 @@ def test_ica_additional(method, tmp_path, short_raw_epochs):
 
     # test if n_components=None works
     ica = ICA(n_components=None, method=method, max_iter=1)
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _baseline_corrected, pytest.warns(UserWarning, match="did not converge"):
         ica.fit(epochs)
     _assert_ica_attributes(ica, epochs.get_data("data"), limits=(0.05, 20))
 
@@ -1032,7 +1036,7 @@ def test_get_explained_variance_ratio(tmp_path, short_raw_epochs):
     with pytest.raises(ValueError, match="ICA must be fitted first"):
         ica.get_explained_variance_ratio(epochs)
 
-    with pytest.warns(RuntimeWarning, match="were baseline-corrected"):
+    with _record_warnings(), _baseline_corrected:
         ica.fit(epochs)
 
     # components = int, ch_type = None
@@ -1255,7 +1259,9 @@ def test_fit_params_epochs_vs_raw(param_name, param_val, tmp_path):
     ica = ICA(n_components=n_components, max_iter=max_iter, method=method)
 
     fit_params = {param_name: param_val}
-    with pytest.warns(RuntimeWarning, match="parameters.*will be ignored"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="parameters.*will be ignored"
+    ):
         ica.fit(inst=epochs, **fit_params)
     assert ica.reject_ == reject
     _assert_ica_attributes(ica)
@@ -1448,7 +1454,7 @@ def test_ica_labels():
         assert key in raw.ch_names
     raw.set_channel_types(rename)
     ica = ICA(n_components=4, max_iter=2, method="fastica", allow_ref_meg=True)
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _record_warnings(), pytest.warns(UserWarning, match="did not converge"):
         ica.fit(raw)
     _assert_ica_attributes(ica)
 
@@ -1473,7 +1479,7 @@ def test_ica_labels():
 
     # derive reference ICA components and append them to raw
     ica_rf = ICA(n_components=2, max_iter=2, allow_ref_meg=True)
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _record_warnings(), pytest.warns(UserWarning, match="did not converge"):
         ica_rf.fit(raw.copy().pick("ref_meg"))
     icacomps = ica_rf.get_sources(raw)
     # rename components so they are auto-detected by find_bads_ref
@@ -1509,7 +1515,7 @@ def test_ica_labels():
     assert_allclose(scores, [0.81, 0.14, 0.37, 0.05], atol=0.03)
 
     ica = ICA(n_components=4, max_iter=2, method="fastica", allow_ref_meg=True)
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _record_warnings(), pytest.warns(UserWarning, match="did not converge"):
         ica.fit(raw, picks="eeg")
     ica.find_bads_muscle(raw)
     assert "muscle" in ica.labels_
diff --git a/mne/preprocessing/tests/test_ssp.py b/mne/preprocessing/tests/test_ssp.py
--- a/mne/preprocessing/tests/test_ssp.py
+++ b/mne/preprocessing/tests/test_ssp.py
@@ -11,6 +11,7 @@
 from mne.datasets import testing
 from mne.io import read_raw_ctf, read_raw_fif
 from mne.preprocessing.ssp import compute_proj_ecg, compute_proj_eog
+from mne.utils import _record_warnings
 
 data_path = Path(__file__).parents[2] / "io" / "tests" / "data"
 raw_fname = data_path / "test_raw.fif"
@@ -72,7 +73,7 @@ def test_compute_proj_ecg(short_raw, average):
     # (first with a call that makes sure we copy the mutable default "reject")
     with pytest.warns(RuntimeWarning, match="longer than the signal"):
         compute_proj_ecg(raw.copy().pick("mag"), l_freq=None, h_freq=None)
-    with pytest.warns(RuntimeWarning, match="No good epochs found"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="No good epochs found"):
         projs, events, drop_log = compute_proj_ecg(
             raw,
             n_mag=2,
@@ -133,7 +134,7 @@ def test_compute_proj_eog(average, short_raw):
             assert proj["explained_var"] > thresh_eeg
     # XXX: better tests
 
-    with pytest.warns(RuntimeWarning, match="longer"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="longer"):
         projs, events = compute_proj_eog(
             raw,
             n_mag=2,
@@ -150,7 +151,9 @@ def test_compute_proj_eog(average, short_raw):
     assert projs == []
 
     raw._data[raw.ch_names.index("EOG 061"), :] = 1.0
-    with pytest.warns(RuntimeWarning, match="filter.*longer than the signal"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="filter.*longer than the signal"
+    ):
         projs, events = compute_proj_eog(raw=raw, tmax=dur_use, ch_name="EOG 061")
 
 
@@ -175,7 +178,7 @@ def test_compute_proj_parallel(short_raw):
             filter_length=100,
         )
     raw_2 = short_raw.copy()
-    with pytest.warns(RuntimeWarning, match="Attenuation"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Attenuation"):
         projs_2, _ = compute_proj_eog(
             raw_2,
             n_eeg=2,
diff --git a/mne/report/tests/test_report.py b/mne/report/tests/test_report.py
--- a/mne/report/tests/test_report.py
+++ b/mne/report/tests/test_report.py
@@ -38,7 +38,7 @@
     CONTENT_ORDER,
     _webp_supported,
 )
-from mne.utils import Bunch
+from mne.utils import Bunch, _record_warnings
 from mne.utils._testing import assert_object_equal
 from mne.viz import plot_alignment
 
@@ -1000,7 +1000,7 @@ def test_manual_report_2d(tmp_path, invisible_fig):
     for ch in evoked_no_ch_locs.info["chs"]:
         ch["loc"][:3] = np.nan
 
-    with pytest.warns(
+    with _record_warnings(), pytest.warns(
         RuntimeWarning, match="No EEG channel locations found, cannot create joint plot"
     ):
         r.add_evokeds(
@@ -1029,7 +1029,9 @@ def test_manual_report_2d(tmp_path, invisible_fig):
     for ch in ica_no_ch_locs.info["chs"]:
         ch["loc"][:3] = np.nan
 
-    with pytest.warns(RuntimeWarning, match="No Magnetometers channel locations"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="No Magnetometers channel locations"
+    ):
         r.add_ica(
             ica=ica_no_ch_locs, picks=[0], inst=raw.copy().load_data(), title="ICA"
         )
@@ -1053,7 +1055,9 @@ def test_manual_report_3d(tmp_path, renderer):
     add_kwargs = dict(
         trans=trans_fname, info=info, subject="sample", subjects_dir=subjects_dir
     )
-    with pytest.warns(RuntimeWarning, match="could not be calculated"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="could not be calculated"
+    ):
         r.add_trans(title="coreg no dig", **add_kwargs)
     with info._unlock():
         info["dig"] = dig
diff --git a/mne/source_space/tests/test_source_space.py b/mne/source_space/tests/test_source_space.py
--- a/mne/source_space/tests/test_source_space.py
+++ b/mne/source_space/tests/test_source_space.py
@@ -699,7 +699,9 @@ def test_source_space_exclusive_complete(src_volume_labels):
     for si, s in enumerate(src):
         assert_allclose(src_full[0]["rr"], s["rr"], atol=1e-6)
     # also check single_volume=True -- should be the same result
-    with pytest.warns(RuntimeWarning, match="Found no usable.*Left-vessel.*"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="Found no usable.*Left-vessel.*"
+    ):
         src_single = setup_volume_source_space(
             src[0]["subject_his_id"],
             7.0,
diff --git a/mne/stats/tests/test_cluster_level.py b/mne/stats/tests/test_cluster_level.py
--- a/mne/stats/tests/test_cluster_level.py
+++ b/mne/stats/tests/test_cluster_level.py
@@ -96,7 +96,9 @@ def test_thresholds(numba_conditional):
     # nan handling in TFCE
     X = np.repeat(X[0], 2, axis=1)
     X[:, 1] = 0
-    with pytest.warns(RuntimeWarning, match="invalid value"):  # NumPy
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="invalid value"
+    ):  # NumPy
         out = permutation_cluster_1samp_test(
             X, seed=0, threshold=dict(start=0, step=0.1), out_type="mask"
         )
@@ -140,7 +142,7 @@ def test_cache_dir(tmp_path, numba_conditional):
         # ensure that non-independence yields warning
         stat_fun = partial(ttest_1samp_no_p, sigma=1e-3)
         random_state = np.random.default_rng(0)
-        with pytest.warns(RuntimeWarning, match="independently"):
+        with _record_warnings(), pytest.warns(RuntimeWarning, match="independently"):
             permutation_cluster_1samp_test(
                 X,
                 buffer_size=10,
@@ -509,7 +511,7 @@ def test_cluster_permutation_with_adjacency(numba_conditional, monkeypatch):
         assert np.min(out_adjacency_6[2]) < 0.05
 
         with pytest.raises(ValueError, match="not compatible"):
-            with pytest.warns(RuntimeWarning, match="No clusters"):
+            with _record_warnings():
                 spatio_temporal_func(
                     X1d_3,
                     n_permutations=50,
diff --git a/mne/tests/test_bem.py b/mne/tests/test_bem.py
--- a/mne/tests/test_bem.py
+++ b/mne/tests/test_bem.py
@@ -44,7 +44,7 @@
 from mne.io import read_info
 from mne.surface import _get_ico_surface, read_surface
 from mne.transforms import translation
-from mne.utils import catch_logging, check_version
+from mne.utils import _record_warnings, catch_logging, check_version
 
 fname_raw = Path(__file__).parents[1] / "io" / "tests" / "data" / "test_raw.fif"
 subjects_dir = testing.data_path(download=False) / "subjects"
@@ -54,6 +54,8 @@
 fname_bem_sol_1 = subjects_dir / "sample" / "bem" / "sample-320-bem-sol.fif"
 fname_dense_head = subjects_dir / "sample" / "bem" / "sample-head-dense.fif"
 
+_few_points = pytest.warns(RuntimeWarning, match="Only .* head digitization")
+
 
 def _compare_bem_surfaces(surfs_1, surfs_2):
     """Compare BEM surfaces."""
@@ -414,7 +416,7 @@ def test_fit_sphere_to_headshape():
 
     #  # Test with 4 points that match a perfect sphere
     dig_kinds = (FIFF.FIFFV_POINT_CARDINAL, FIFF.FIFFV_POINT_EXTRA)
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _few_points:
         r, oh, od = fit_sphere_to_headshape(info, dig_kinds=dig_kinds, units="m")
     kwargs = dict(rtol=1e-3, atol=1e-5)
     assert_allclose(r, rad, **kwargs)
@@ -424,7 +426,7 @@ def test_fit_sphere_to_headshape():
     # Test with all points
     dig_kinds = ("cardinal", FIFF.FIFFV_POINT_EXTRA, "eeg")
     kwargs = dict(rtol=1e-3, atol=1e-3)
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _few_points:
         r, oh, od = fit_sphere_to_headshape(info, dig_kinds=dig_kinds, units="m")
     assert_allclose(r, rad, **kwargs)
     assert_allclose(oh, center, **kwargs)
@@ -432,7 +434,7 @@ def test_fit_sphere_to_headshape():
 
     # Test with some noisy EEG points only.
     dig_kinds = "eeg"
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _few_points:
         r, oh, od = fit_sphere_to_headshape(info, dig_kinds=dig_kinds, units="m")
     kwargs = dict(rtol=1e-3, atol=1e-2)
     assert_allclose(r, rad, **kwargs)
@@ -446,7 +448,7 @@ def test_fit_sphere_to_headshape():
         d["r"] -= center
         d["r"] *= big_rad / rad
         d["r"] += center
-    with pytest.warns(RuntimeWarning, match="Estimated head radius"):
+    with _few_points, pytest.warns(RuntimeWarning, match="Estimated head radius"):
         r, oh, od = fit_sphere_to_headshape(info_big, dig_kinds=dig_kinds, units="mm")
     assert_allclose(oh, center * 1000, atol=1e-3)
     assert_allclose(r, big_rad * 1000, atol=1e-3)
@@ -459,27 +461,31 @@ def test_fit_sphere_to_headshape():
     for d in info_shift["dig"]:
         d["r"] -= center
         d["r"] += shift_center
-    with pytest.warns(RuntimeWarning, match="from head frame origin"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="from head frame origin"
+    ):
         r, oh, od = fit_sphere_to_headshape(info_shift, dig_kinds=dig_kinds, units="m")
     assert_allclose(oh, shift_center, atol=1e-6)
     assert_allclose(r, rad, atol=1e-6)
 
     # Test "auto" mode (default)
     # Should try "extra", fail, and go on to EEG
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _few_points:
         r, oh, od = fit_sphere_to_headshape(info, units="m")
     kwargs = dict(rtol=1e-3, atol=1e-3)
     assert_allclose(r, rad, **kwargs)
     assert_allclose(oh, center, **kwargs)
     assert_allclose(od, dev_center, **kwargs)
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _few_points:
         r2, oh2, od2 = fit_sphere_to_headshape(info, units="m")
     assert_allclose(r, r2, atol=1e-7)
     assert_allclose(oh, oh2, atol=1e-7)
     assert_allclose(od, od2, atol=1e-7)
     # this one should pass, 1 EXTRA point and 3 EEG (but the fit is terrible)
     info = Info(dig=dig[:7], dev_head_t=dev_head_t)
-    with pytest.warns(RuntimeWarning, match="Only .* head digitization"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="Estimated head radius"
+    ):
         r, oh, od = fit_sphere_to_headshape(info, units="m")
     # this one should fail, 1 EXTRA point and 3 EEG (but the fit is terrible)
     info = Info(dig=dig[:6], dev_head_t=dev_head_t)
@@ -499,12 +505,12 @@ def test_io_head_bem(tmp_path):
 
     with pytest.raises(ValueError, match="topological defects:"):
         write_head_bem(fname_defect, head["rr"], head["tris"])
-    with pytest.warns(RuntimeWarning, match="topological defects:"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="topological defects:"):
         write_head_bem(fname_defect, head["rr"], head["tris"], on_defects="warn")
     # test on_defects in read_bem_surfaces
     with pytest.raises(ValueError, match="topological defects:"):
         read_bem_surfaces(fname_defect)
-    with pytest.warns(RuntimeWarning, match="topological defects:"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="topological defects:"):
         head_defect = read_bem_surfaces(fname_defect, on_defects="warn")[0]
 
     assert head["id"] == head_defect["id"] == FIFF.FIFFV_BEM_SURF_ID_HEAD
@@ -550,12 +556,14 @@ def _decimate_surface(points, triangles, n_triangles):
 
     # These are ignorable
     monkeypatch.setattr(mne.bem, "_tri_levels", dict(sparse=315))
-    with pytest.warns(RuntimeWarning, match=".*have fewer than three.*"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match=".*have fewer than three.*"
+    ):
         make_scalp_surfaces(subject, subjects_dir, force=True, overwrite=True)
     (surf,) = read_bem_surfaces(sparse_path, on_defects="ignore")
     assert len(surf["tris"]) == 315
     monkeypatch.setattr(mne.bem, "_tri_levels", dict(sparse=319))
-    with pytest.warns(RuntimeWarning, match=".*is not complete.*"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match=".*is not complete.*"):
         make_scalp_surfaces(subject, subjects_dir, force=True, overwrite=True)
     (surf,) = read_bem_surfaces(sparse_path, on_defects="ignore")
     assert len(surf["tris"]) == 319
diff --git a/mne/tests/test_chpi.py b/mne/tests/test_chpi.py
--- a/mne/tests/test_chpi.py
+++ b/mne/tests/test_chpi.py
@@ -43,7 +43,13 @@
 )
 from mne.simulation import add_chpi
 from mne.transforms import _angle_between_quats, rot_to_quat
-from mne.utils import assert_meg_snr, catch_logging, object_diff, verbose
+from mne.utils import (
+    _record_warnings,
+    assert_meg_snr,
+    catch_logging,
+    object_diff,
+    verbose,
+)
 from mne.viz import plot_head_positions
 
 base_dir = Path(__file__).parents[1] / "io" / "tests" / "data"
@@ -366,7 +372,7 @@ def test_calculate_chpi_positions_vv():
         ]
     )
     raw_bad.pick([raw_bad.ch_names[pick] for pick in picks])
-    with pytest.warns(RuntimeWarning, match="Discrepancy"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Discrepancy"):
         with catch_logging() as log_file:
             _calculate_chpi_positions(raw_bad, t_step_min=1.0, verbose=True)
     # ignore HPI info header and [done] footer
diff --git a/mne/tests/test_cov.py b/mne/tests/test_cov.py
--- a/mne/tests/test_cov.py
+++ b/mne/tests/test_cov.py
@@ -352,7 +352,7 @@ def test_cov_estimation_on_raw(method, tmp_path):
     assert_snr(cov.data, cov_mne.data[:5, :5], 90)  # cutoff samps
     # make sure we get a warning with too short a segment
     raw_2 = read_raw_fif(raw_fname).crop(0, 1)
-    with pytest.warns(RuntimeWarning, match="Too few samples"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Too few samples"):
         cov = compute_raw_covariance(raw_2, method=method, method_params=method_params)
     # no epochs found due to rejection
     pytest.raises(
@@ -384,7 +384,7 @@ def test_cov_estimation_on_raw_reg():
         raw.info["sfreq"] /= 10.0
     raw = RawArray(raw._data[:, ::10].copy(), raw.info)  # decimate for speed
     cov_mne = read_cov(erm_cov_fname)
-    with pytest.warns(RuntimeWarning, match="Too few samples"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Too few samples"):
         # "diagonal_fixed" is much faster. Use long epochs for speed.
         cov = compute_raw_covariance(raw, tstep=5.0, method="diagonal_fixed")
     assert_snr(cov.data, cov_mne.data, 5)
@@ -891,13 +891,13 @@ def test_cov_ctf():
     for comp in [0, 1]:
         raw.apply_gradient_compensation(comp)
         epochs = Epochs(raw, events, None, -0.2, 0.2, preload=True)
-        with pytest.warns(RuntimeWarning, match="Too few samples"):
+        with _record_warnings(), pytest.warns(RuntimeWarning, match="Too few samples"):
             noise_cov = compute_covariance(epochs, tmax=0.0, method=["empirical"])
         prepare_noise_cov(noise_cov, raw.info, ch_names)
 
     raw.apply_gradient_compensation(0)
     epochs = Epochs(raw, events, None, -0.2, 0.2, preload=True)
-    with pytest.warns(RuntimeWarning, match="Too few samples"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="Too few samples"):
         noise_cov = compute_covariance(epochs, tmax=0.0, method=["empirical"])
     raw.apply_gradient_compensation(1)
 
diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py
--- a/mne/tests/test_epochs.py
+++ b/mne/tests/test_epochs.py
@@ -64,6 +64,7 @@
 from mne.preprocessing import maxwell_filter
 from mne.utils import (
     _dt_to_stamp,
+    _record_warnings,
     assert_meg_snr,
     catch_logging,
     object_diff,
@@ -2291,7 +2292,7 @@ def test_crop(tmp_path):
         reject=reject,
         flat=flat,
     )
-    with pytest.warns(RuntimeWarning, match="tmax is set to"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="tmax is set to"):
         epochs2.crop(-20, 200)
 
     # indices for slicing
@@ -3610,7 +3611,7 @@ def test_concatenate_epochs():
 
     # check concatenating epochs where one of the objects is empty
     epochs2 = epochs.copy()[:0]
-    with pytest.warns(RuntimeWarning, match="was empty"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="was empty"):
         concatenate_epochs([epochs, epochs2])
 
     # check concatenating epochs results are chronologically ordered
@@ -4221,7 +4222,7 @@ def test_no_epochs(tmp_path):
     # and with no epochs remaining
     raw.info["bads"] = []
     epochs = mne.Epochs(raw, events, reject=reject)
-    with pytest.warns(RuntimeWarning, match="no data"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="no data"):
         epochs.save(tmp_path / "sample-epo.fif", overwrite=True)
     assert len(epochs) == 0  # all dropped
 
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -34,7 +34,7 @@
 from mne._fiff.constants import FIFF
 from mne.evoked import Evoked, EvokedArray, _get_peak
 from mne.io import read_raw_fif
-from mne.utils import grand_average
+from mne.utils import _record_warnings, grand_average
 
 base_dir = Path(__file__).parents[1] / "io" / "tests" / "data"
 fname = base_dir / "test-ave.fif"
@@ -799,7 +799,7 @@ def test_time_as_index_and_crop():
     )
     evoked.crop(evoked.tmin, evoked.tmax, include_tmax=False)
     n_times = len(evoked.times)
-    with pytest.warns(RuntimeWarning, match="tmax is set to"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="tmax is set to"):
         evoked.crop(tmin, tmax, include_tmax=False)
     assert len(evoked.times) == n_times
     assert_allclose(evoked.times[[0, -1]], [tmin, tmax - delta], atol=atol)
diff --git a/mne/tests/test_source_estimate.py b/mne/tests/test_source_estimate.py
--- a/mne/tests/test_source_estimate.py
+++ b/mne/tests/test_source_estimate.py
@@ -399,7 +399,7 @@ def test_stc_snr():
     assert (stc.data < 0).any()
     with pytest.warns(RuntimeWarning, match="nAm"):
         stc.estimate_snr(evoked.info, fwd, cov)  # dSPM
-    with pytest.warns(RuntimeWarning, match="free ori"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="free ori"):
         abs(stc).estimate_snr(evoked.info, fwd, cov)
     stc = apply_inverse(evoked, inv, method="MNE")
     snr = stc.estimate_snr(evoked.info, fwd, cov)
diff --git a/mne/time_frequency/tests/test_spectrum.py b/mne/time_frequency/tests/test_spectrum.py
--- a/mne/time_frequency/tests/test_spectrum.py
+++ b/mne/time_frequency/tests/test_spectrum.py
@@ -11,6 +11,7 @@
 from mne import Annotations
 from mne.time_frequency import read_spectrum
 from mne.time_frequency.spectrum import EpochsSpectrumArray, SpectrumArray
+from mne.utils import _record_warnings
 
 
 def test_compute_psd_errors(raw):
@@ -273,7 +274,7 @@ def test_spectrum_kwarg_triaging(raw):
     regex = r"legacy plot_psd\(\) method.*unexpected keyword.*'axes'.*Try rewriting"
     _, axes = plt.subplots(1, 2)
     # `axes` is the new param name: technically only valid for Spectrum.plot()
-    with pytest.warns(RuntimeWarning, match=regex):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match=regex):
         raw.plot_psd(axes=axes)
     # `ax` is the correct legacy param name
     with pytest.warns(FutureWarning, match="amplitude='auto'"):
diff --git a/mne/utils/tests/test_check.py b/mne/utils/tests/test_check.py
--- a/mne/utils/tests/test_check.py
+++ b/mne/utils/tests/test_check.py
@@ -27,6 +27,7 @@
     _check_subject,
     _on_missing,
     _path_like,
+    _record_warnings,
     _safe_input,
     _suggest,
     _validate_type,
@@ -368,7 +369,7 @@ def test_check_sphere_verbose():
     info = mne.io.read_info(fname_raw)
     with info._unlock():
         info["dig"] = info["dig"][:20]
-    with pytest.warns(RuntimeWarning, match="may be inaccurate"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="may be inaccurate"):
         _check_sphere("auto", info)
     with mne.use_log_level("error"):
         _check_sphere("auto", info)
diff --git a/mne/utils/tests/test_logging.py b/mne/utils/tests/test_logging.py
--- a/mne/utils/tests/test_logging.py
+++ b/mne/utils/tests/test_logging.py
@@ -63,7 +63,7 @@ def test_frame_info(capsys, monkeypatch):
 
 def test_how_to_deal_with_warnings():
     """Test filter some messages out of warning records."""
-    with pytest.warns(UserWarning, match="bb") as w:
+    with pytest.warns(Warning, match="(bb|aa) warning") as w:
         warnings.warn("aa warning", UserWarning)
         warnings.warn("bb warning", UserWarning)
         warnings.warn("bb warning", RuntimeWarning)
diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py
--- a/mne/viz/tests/test_epochs.py
+++ b/mne/viz/tests/test_epochs.py
@@ -17,6 +17,7 @@
 from mne import Epochs, EpochsArray, create_info
 from mne.datasets import testing
 from mne.event import make_fixed_length_events
+from mne.utils import _record_warnings
 from mne.viz import plot_drop_log
 
 
@@ -52,13 +53,13 @@ def test_plot_epochs_basic(epochs, epochs_full, noise_cov_io, capsys, browser_ba
     browser_backend._close_all()
     # add a channel to cov['bads']
     noise_cov_io["bads"] = [epochs.ch_names[1]]
-    with pytest.warns(RuntimeWarning, match="projection"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="projection"):
         epochs.plot(noise_cov=noise_cov_io)
     browser_backend._close_all()
     # have a data channel missing from the covariance
     noise_cov_io["names"] = noise_cov_io["names"][:306]
     noise_cov_io["data"] = noise_cov_io["data"][:306][:306]
-    with pytest.warns(RuntimeWarning, match="projection"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="projection"):
         epochs.plot(noise_cov=noise_cov_io)
     browser_backend._close_all()
     # other options
@@ -300,7 +301,9 @@ def test_plot_epochs_image(epochs):
         picks=[0, 1], order=lambda times, data: np.arange(len(data))[::-1]
     )
     # test warning
-    with pytest.warns(RuntimeWarning, match="Only one channel in group"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="Only one channel in group"
+    ):
         epochs.plot_image(picks=[1], combine="mean")
     # group_by should be a dict
     with pytest.raises(TypeError, match="dict or None"):
@@ -418,7 +421,7 @@ def test_plot_psd_epochs(epochs):
     err_str = "for channel %s" % epochs.ch_names[2]
     epochs.get_data(copy=False)[0, 2, :] = 0
     for dB in [True, False]:
-        with pytest.warns(UserWarning, match=err_str):
+        with _record_warnings(), pytest.warns(UserWarning, match=err_str):
             epochs.compute_psd().plot(dB=dB)
 
 
@@ -492,7 +495,7 @@ def test_plot_psd_epochs_ctf(raw_ctf):
     epochs = Epochs(raw_ctf, evts, preload=True)
     old_defaults = dict(picks="data", exclude="bads")
     # EEG060 is flat in this dataset
-    with pytest.warns(UserWarning, match="for channel EEG060"):
+    with _record_warnings(), pytest.warns(UserWarning, match="for channel EEG060"):
         spectrum = epochs.compute_psd()
         for dB in [True, False]:
             spectrum.plot(dB=dB)
diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py
--- a/mne/viz/tests/test_evoked.py
+++ b/mne/viz/tests/test_evoked.py
@@ -34,7 +34,7 @@
 from mne.datasets import testing
 from mne.io import read_raw_fif
 from mne.stats.parametric import _parametric_ci
-from mne.utils import catch_logging
+from mne.utils import _record_warnings, catch_logging
 from mne.viz import plot_compare_evokeds, plot_evoked_white
 from mne.viz.utils import _fake_click, _get_cmap
 
@@ -119,7 +119,7 @@ def test_plot_evoked_cov():
     epochs = Epochs(raw, events, picks=default_picks)
     cov = compute_covariance(epochs)
     evoked_sss = epochs.average()
-    with pytest.warns(RuntimeWarning, match="relative scaling"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="relative scaling"):
         evoked_sss.plot(noise_cov=cov, time_unit="s")
     plt.close("all")
 
@@ -333,7 +333,7 @@ def test_plot_evoked_image():
         mask=np.ones(evoked.data.shape).astype(bool),
         time_unit="s",
     )
-    with pytest.warns(RuntimeWarning, match="not adding contour"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="not adding contour"):
         evoked.plot_image(picks=[1, 2], mask=None, mask_style="both", time_unit="s")
     with pytest.raises(ValueError, match="must have the same shape"):
         evoked.plot_image(mask=evoked.data[1:, 1:] > 0, time_unit="s")
diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py
--- a/mne/viz/tests/test_ica.py
+++ b/mne/viz/tests/test_ica.py
@@ -157,7 +157,7 @@ def test_plot_ica_properties():
     )
 
     ica = ICA(noise_cov=read_cov(cov_fname), n_components=2, max_iter=1, random_state=0)
-    with pytest.warns(RuntimeWarning, match="projection"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="projection"):
         ica.fit(raw)
 
     # test _create_properties_layout
@@ -240,7 +240,7 @@ def test_plot_ica_properties():
     # Test handling of zeros
     ica = ICA(random_state=0, max_iter=1)
     epochs.pick(pick_names)
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _record_warnings(), pytest.warns(UserWarning, match="did not converge"):
         ica.fit(epochs)
     epochs._data[0] = 0
     # Usually UserWarning: Infinite value .* for epo
@@ -254,7 +254,7 @@ def test_plot_ica_properties():
     raw_annot.pick(np.arange(10))
     raw_annot.del_proj()
 
-    with pytest.warns(UserWarning, match="did not converge"):
+    with _record_warnings(), pytest.warns(UserWarning, match="did not converge"):
         ica.fit(raw_annot)
     # drop bad data segments
     fig = ica.plot_properties(raw_annot, picks=[0, 1], **topoargs)
diff --git a/mne/viz/tests/test_misc.py b/mne/viz/tests/test_misc.py
--- a/mne/viz/tests/test_misc.py
+++ b/mne/viz/tests/test_misc.py
@@ -30,6 +30,7 @@
 from mne.io import read_raw_fif
 from mne.minimum_norm import read_inverse_operator
 from mne.time_frequency import CrossSpectralDensity
+from mne.utils import _record_warnings
 from mne.viz import (
     plot_bem,
     plot_chpi_snr,
@@ -214,7 +215,9 @@ def test_plot_events():
     assert fig.axes[0].get_legend() is not None
     with pytest.warns(RuntimeWarning, match="Color was not assigned"):
         plot_events(events, raw.info["sfreq"], raw.first_samp, color=color)
-    with pytest.warns(RuntimeWarning, match=r"vent \d+ missing from event_id"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match=r"vent \d+ missing from event_id"
+    ):
         plot_events(
             events,
             raw.info["sfreq"],
@@ -223,7 +226,7 @@ def test_plot_events():
             color=color,
         )
     multimatch = r"event \d+ missing from event_id|in the color dict but is"
-    with pytest.warns(RuntimeWarning, match=multimatch):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match=multimatch):
         plot_events(
             events,
             raw.info["sfreq"],
@@ -243,7 +246,9 @@ def test_plot_events():
             on_missing="ignore",
         )
     extra_id = {"aud_l": 1, "missing": 111}
-    with pytest.warns(RuntimeWarning, match="from event_id is not present in"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="from event_id is not present in"
+    ):
         plot_events(
             events,
             raw.info["sfreq"],
@@ -251,7 +256,7 @@ def test_plot_events():
             event_id=extra_id,
             on_missing="warn",
         )
-    with pytest.warns(RuntimeWarning, match="event 2 missing"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="event 2 missing"):
         plot_events(
             events,
             raw.info["sfreq"],
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -973,7 +973,9 @@ def test_plot_raw_psd(raw, raw_orig):
     # with channel information not available
     for idx in range(len(raw.info["chs"])):
         raw.info["chs"][idx]["loc"] = np.zeros(12)
-    with pytest.warns(RuntimeWarning, match="locations not available"):
+    with _record_warnings(), pytest.warns(
+        RuntimeWarning, match="locations not available"
+    ):
         raw.compute_psd().plot(spatial_colors=True, average=False)
     # with a flat channel
     raw[5, :] = 0
diff --git a/mne/viz/tests/test_topo.py b/mne/viz/tests/test_topo.py
--- a/mne/viz/tests/test_topo.py
+++ b/mne/viz/tests/test_topo.py
@@ -325,7 +325,7 @@ def test_plot_tfr_topo():
     # test opening tfr by clicking
     num_figures_before = len(plt.get_fignums())
     # could use np.reshape(fig.axes[-1].images[0].get_extent(), (2, 2)).mean(1)
-    with pytest.warns(RuntimeWarning, match="not masking"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="not masking"):
         _fake_click(fig, fig.axes[0], (0.08, 0.65))
     assert num_figures_before + 1 == len(plt.get_fignums())
     plt.close("all")
@@ -349,7 +349,7 @@ def test_plot_tfr_topo():
     vmin, vmax = 0.0, 2.0
     fig, ax = plt.subplots()
     tmin, tmax = epochs.times[0], epochs.times[-1]
-    with pytest.warns(RuntimeWarning, match="not masking"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="not masking"):
         _imshow_tfr(
             ax,
             3,
@@ -372,7 +372,7 @@ def test_plot_tfr_topo():
     # ValueError when freq[0] == 0 and yscale == 'log'
     these_freqs = freqs[:3].copy()
     these_freqs[0] = 0
-    with pytest.warns(RuntimeWarning, match="not masking"):
+    with _record_warnings(), pytest.warns(RuntimeWarning, match="not masking"):
         pytest.raises(
             ValueError,
             _imshow_tfr,