Skip to content

Commit 1ef84cf

Browse files
committed
Only patch linecache in 3.12 for Windows
- patching it for Python 3.12 under Posix could break pytest tests under some conditions
1 parent 23db235 commit 1ef84cf

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The released versions correspond to PyPI releases.
2020
### Fixes
2121
* fixed a problem with flushing if writing over the buffer end
2222
(see [#1120](../../issues/1120))
23+
* fixed a regression that could break tests under Posix in Python 3.12
24+
(see [#1126](../../issues/1126))
2325

2426
## [Version 5.7.4](https://pypi.python.org/pypi/pyfakefs/5.7.4) (2025-01-14)
2527
Minor bugfix release.

pyfakefs/fake_filesystem_unittest.py

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,54 @@ def stop_patching(self):
130130
tempfile.tempdir = None
131131

132132

133+
class LineCachePatcher:
134+
"""Handles linecache patching for newer Python versions."""
135+
136+
def __init__(self):
137+
self.linecache_updatecache = None
138+
self.linecache_checkcache = None
139+
140+
def start_patching(self):
141+
if self.linecache_updatecache is not None:
142+
return
143+
144+
def checkcache(filename=None):
145+
"""Calls the original linecache.checkcache making sure no fake OS calls
146+
are used."""
147+
with use_original_os():
148+
return self.linecache_checkcache(filename)
149+
150+
def updatecache(filename, module_globals=None):
151+
"""Calls the original linecache.updatecache making sure no fake OS calls
152+
are used."""
153+
154+
with use_original_os():
155+
# workaround for updatecache problem with pytest under Windows, see #1096
156+
if not filename.endswith(r"pytest.exe\__main__.py"):
157+
return self.linecache_updatecache(filename, module_globals)
158+
return []
159+
160+
if (
161+
sys.version_info >= (3, 13)
162+
or sys.platform == "win32"
163+
and sys.version_info >= (3, 12)
164+
):
165+
# in linecache, 'os' is now imported locally, which involves the
166+
# dynamic patcher, therefore we patch the affected functions
167+
self.linecache_updatecache = linecache.updatecache
168+
linecache.updatecache = updatecache
169+
self.linecache_checkcache = linecache.checkcache
170+
linecache.checkcache = checkcache
171+
172+
def stop_patching(self):
173+
if self.linecache_updatecache is None:
174+
return
175+
linecache.updatecache = self.linecache_updatecache
176+
linecache.checkcache = self.linecache_checkcache
177+
self.linecache_updatecache = None
178+
self.linecache_checkcache = None
179+
180+
133181
def patchfs(
134182
_func: Optional[Callable] = None,
135183
*,
@@ -616,9 +664,8 @@ def __init__(
616664
# save the original open function for use in pytest plugin
617665
self.original_open = open
618666
self.patch_open_code = patch_open_code
619-
self.linecache_updatecache = None
620-
self.linecache_checkcache = None
621667
self.tempfile_patcher = TempfilePatcher()
668+
self.linecache_patcher = LineCachePatcher()
622669

623670
if additional_skip_names is not None:
624671
skip_names = [
@@ -692,21 +739,6 @@ def __init__(
692739
self.has_copy_file_range = False
693740
self.has_copy_file = False
694741

695-
def checkcache(self, filename=None):
696-
"""Calls the original linecache.checkcache making sure no fake OS calls
697-
are used."""
698-
with use_original_os():
699-
return self.linecache_checkcache(filename)
700-
701-
def updatecache(self, filename, module_globals=None):
702-
"""Calls the original linecache.updatecache making sure no fake OS calls
703-
are used."""
704-
with use_original_os():
705-
# workaround for updatecache problem with pytest under Windows, see #1096
706-
if not filename.endswith(r"pytest.exe\__main__.py"):
707-
return self.linecache_updatecache(filename, module_globals)
708-
return []
709-
710742
@classmethod
711743
def clear_fs_cache(cls) -> None:
712744
"""Clear the module cache."""
@@ -1028,14 +1060,7 @@ def start_patching(self) -> None:
10281060
self._patching = True
10291061
self._paused = False
10301062

1031-
if sys.version_info >= (3, 12):
1032-
# in linecache, 'os' is now imported locally, which involves the
1033-
# dynamic patcher, therefore we patch the affected functions
1034-
self.linecache_updatecache = linecache.updatecache
1035-
linecache.updatecache = self.updatecache
1036-
self.linecache_checkcache = linecache.checkcache
1037-
linecache.checkcache = self.checkcache
1038-
1063+
self.linecache_patcher.start_patching()
10391064
self.tempfile_patcher.start_patching()
10401065

10411066
self.patch_modules()
@@ -1156,9 +1181,7 @@ def stop_patching(self, temporary=False) -> None:
11561181
self._dyn_patcher.cleanup()
11571182
sys.meta_path.pop(0)
11581183
self.tempfile_patcher.stop_patching()
1159-
if self.linecache_updatecache is not None:
1160-
linecache.updatecache = self.linecache_updatecache
1161-
linecache.checkcache = self.linecache_checkcache
1184+
self.linecache_patcher.stop_patching()
11621185
self._set_glob_os_functions()
11631186

11641187
@property

0 commit comments

Comments
 (0)