@@ -1238,6 +1238,86 @@ def test_find_matching_paths(return_bids_test_dir):
12381238 bids_path_01 .fpath .unlink () # clean up created file
12391239
12401240
1241+ def test_return_root_paths_entity_aware (tmp_path ):
1242+ """Test that `_return_root_paths` respects `entities['subject']` and
1243+ returns only paths under that subject when provided.
1244+
1245+ This validates the entity-aware optimization added to reduce filesystem
1246+ scanning when the subject (and optionally session) is known.
1247+ """
1248+ from mne_bids .path import _return_root_paths
1249+
1250+ root = tmp_path / "bids"
1251+ # Create two subjects each with meg and eeg directories and a file
1252+ for subj in ("subjA" , "subjB" ):
1253+ for dtype in ("meg" , "eeg" ):
1254+ p = BIDSPath (subject = subj , session = "sesA" , datatype = dtype , root = root )
1255+ p .mkdir (exist_ok = True )
1256+ # create a predictable dummy data file so glob matches reliably
1257+ ext = "fif" if dtype == "meg" else "edf"
1258+ fname = f"sub-{ subj } _ses-sesA_{ dtype } .{ ext } "
1259+ (p .directory / fname ).touch ()
1260+
1261+ # Full scan (no entities) should return files for both subjects
1262+ # Pass root as string to match how glob.iglob expects root_dir
1263+ root_str = str (root )
1264+ # ensure directories exist
1265+ assert (root / "sub-subjA" ).exists ()
1266+ all_paths = _return_root_paths (root_str , datatype = ("meg" , "eeg" ), ignore_json = True )
1267+ assert len (all_paths ) >= 2
1268+
1269+ # Entity-aware scan for subjA should only return files under sub-subjA
1270+ subj_paths = _return_root_paths (root_str , datatype = ("meg" , "eeg" ), ignore_json = True , entities = {"subject" : "subjA" })
1271+ assert len (subj_paths ) < len (all_paths )
1272+ assert all ("sub-subjA" in str (p ) for p in subj_paths )
1273+
1274+
1275+ def test_return_root_paths_monkeypatched_glob (monkeypatch ):
1276+ """Unit test for `_return_root_paths` that monkeypatches glob.iglob.
1277+
1278+ This avoids relying on filesystem behavior and asserts that the
1279+ function converts strings returned by glob.iglob into Path objects and
1280+ applies the `ignore_json` filter correctly.
1281+ """
1282+ from mne_bids .path import _return_root_paths
1283+
1284+ fake_root = "/fake/root"
1285+
1286+ # Simulate iglob returning a mix of json and data files (relative paths)
1287+ fake_results = [
1288+ "sub-subjA/ses-sesA/meg/sub-subjA_ses-sesA_meg.fif" ,
1289+ "sub-subjA/ses-sesA/meg/sub-subjA_ses-sesA_meg.json" ,
1290+ "sub-subjB/ses-sesA/eeg/sub-subjB_ses-sesA_eeg.edf" ,
1291+ ]
1292+
1293+ def fake_iglob (pattern , root_dir = None , recursive = False ):
1294+ # ensure pattern was constructed as relative to root
1295+ assert str (root_dir ) == fake_root
1296+ return iter (fake_results )
1297+
1298+ # monkeypatch glob.iglob to return our fake results
1299+ monkeypatch .setattr ("glob.iglob" , fake_iglob )
1300+
1301+ # monkeypatch _filter_paths_optimized to avoid filesystem checks
1302+ from mne_bids import path as mb_path
1303+
1304+ def fake_filter (paths , ignore_json ):
1305+ # convert returned strings into Path objects rooted at fake_root
1306+ return [Path (fake_root , p ) for p in fake_results if (not ignore_json ) or (not p .endswith ('.json' ))]
1307+
1308+ monkeypatch .setattr (mb_path , "_filter_paths_optimized" , fake_filter )
1309+
1310+ # ignore_json=True should filter out the .json entry
1311+ paths = _return_root_paths (fake_root , datatype = ("meg" , "eeg" ), ignore_json = True , entities = {"subject" : "subjA" })
1312+ assert all (isinstance (p , Path ) for p in paths )
1313+ # only the .fif entry for subjA should remain
1314+ assert any (p .suffix == ".fif" and "sub-subjA" in str (p ) for p in paths )
1315+
1316+ # ignore_json=False should include the .json entry
1317+ paths_all = _return_root_paths (fake_root , datatype = ("meg" , "eeg" ), ignore_json = False , entities = {"subject" : "subjA" })
1318+ assert any (str (p ).endswith (".json" ) for p in paths_all )
1319+
1320+
12411321@pytest .mark .filterwarnings (warning_str ["meas_date_set_to_none" ])
12421322@pytest .mark .filterwarnings (warning_str ["channel_unit_changed" ])
12431323@testing .requires_testing_data
0 commit comments