Skip to content

Commit 6f04a45

Browse files
committed
Performance: do not reload tempfile
- patch tempfile instead directly (Posix only) - decrease allowed time for performance tests
1 parent 5ffdf97 commit 6f04a45

File tree

4 files changed

+68
-29
lines changed

4 files changed

+68
-29
lines changed

CHANGES.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ The released versions correspond to PyPI releases.
1414

1515
## Unreleased
1616

17+
### Performance
18+
* avoid reloading `tempfile` in Posix systems
19+
1720
### Infrastructure
1821
* use trusted publisher for release (see https://docs.pypi.org/trusted-publishers/)
1922

@@ -33,7 +36,9 @@ Adds official Python 3.13 support, improves OS emulation behavior.
3336
* the `additional_skip_names` parameter now works with more modules (see [#1023](../../issues/1023))
3437
* added support for `os.fchmod`, allow file descriptor argument for `os.chmod` only for POSIX
3538
for Python < 3.13
36-
* avoid reloading `glob` in Python 3.13 (did affect test performance)
39+
40+
### Performance
41+
* avoid reloading `glob` in Python 3.13
3742

3843
### Fixes
3944
* removing files while iterating over `scandir` results is now possible (see [#1051](../../issues/1051))
@@ -574,6 +579,8 @@ release.
574579
default to avoid a large performance impact. An additional parameter
575580
`patch_default_args` has been added that switches this behavior on
576581
(see [#567](../../issues/567)).
582+
583+
### Performance
577584
* Added performance improvements in the test setup, including caching the
578585
unpatched modules
579586

pyfakefs/fake_filesystem_unittest.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,41 @@
9595
PATH_MODULE = "ntpath" if sys.platform == "win32" else "posixpath"
9696

9797

98+
class TempfilePatcher:
99+
"""Handles tempfile patching for Posix systems."""
100+
101+
def __init__(self):
102+
self.tempfile_cleanup = None
103+
104+
def start_patching(self):
105+
if self.tempfile_cleanup is not None:
106+
return
107+
if sys.version_info >= (3, 12):
108+
109+
def cleanup(self_, windows=(os.name == "nt"), unlink=None):
110+
self.tempfile_cleanup(self_, windows, unlink or os.unlink)
111+
112+
self.tempfile_cleanup = tempfile._TemporaryFileCloser.cleanup # type: ignore[module-attr]
113+
tempfile._TemporaryFileCloser.cleanup = cleanup # type: ignore[module-attr]
114+
elif sys.platform != "win32":
115+
116+
def close(self_, unlink=None):
117+
self.tempfile_cleanup(self_, unlink or os.unlink)
118+
119+
self.tempfile_cleanup = tempfile._TemporaryFileCloser.close # type: ignore[module-attr]
120+
tempfile._TemporaryFileCloser.close = close # type: ignore[module-attr]
121+
122+
def stop_patching(self):
123+
if self.tempfile_cleanup is None:
124+
return
125+
if sys.version_info < (3, 12):
126+
tempfile._TemporaryFileCloser.close = self.tempfile_cleanup # type: ignore[module-attr]
127+
else:
128+
tempfile._TemporaryFileCloser.cleanup = self.tempfile_cleanup # type: ignore[module-attr]
129+
# reset the cached tempdir in tempfile
130+
tempfile.tempdir = None
131+
132+
98133
def patchfs(
99134
_func: Optional[Callable] = None,
100135
*,
@@ -576,6 +611,7 @@ def __init__(
576611
self.patch_open_code = patch_open_code
577612
self.linecache_updatecache = None
578613
self.linecache_checkcache = None
614+
self.tempfile_patcher = TempfilePatcher()
579615

580616
if additional_skip_names is not None:
581617
skip_names = [
@@ -590,9 +626,7 @@ def __init__(
590626
self._init_fake_module_classes()
591627

592628
# reload tempfile under posix to patch default argument
593-
self.modules_to_reload: List[ModuleType] = (
594-
[] if sys.platform == "win32" else [tempfile]
595-
)
629+
self.modules_to_reload: List[ModuleType] = []
596630
if modules_to_reload is not None:
597631
self.modules_to_reload.extend(modules_to_reload)
598632
self.patch_default_args = patch_default_args
@@ -977,6 +1011,8 @@ def start_patching(self) -> None:
9771011
self.linecache_checkcache = linecache.checkcache
9781012
linecache.checkcache = self.checkcache
9791013

1014+
self.tempfile_patcher.start_patching()
1015+
9801016
self.patch_modules()
9811017
self.patch_functions()
9821018
self.patch_defaults()
@@ -1075,6 +1111,7 @@ def stop_patching(self, temporary=False) -> None:
10751111
if self.use_dynamic_patch and self._dyn_patcher:
10761112
self._dyn_patcher.cleanup()
10771113
sys.meta_path.pop(0)
1114+
self.tempfile_patcher.stop_patching()
10781115
if self.linecache_updatecache is not None:
10791116
linecache.updatecache = self.linecache_updatecache
10801117
linecache.checkcache = self.linecache_checkcache

pyfakefs/tests/fake_filesystem_unittest_test.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -541,25 +541,20 @@ def test_non_root_behavior(self):
541541

542542
class PauseResumeTest(fake_filesystem_unittest.TestCase):
543543
def setUp(self):
544-
self.real_temp_file = None
545544
self.setUpPyfakefs()
546545

547-
def tearDown(self):
548-
if self.real_temp_file is not None:
549-
self.real_temp_file.close()
550-
551546
def test_pause_resume(self):
552547
fake_temp_file = tempfile.NamedTemporaryFile()
553548
self.assertTrue(self.fs.exists(fake_temp_file.name))
554549
self.assertTrue(os.path.exists(fake_temp_file.name))
555550
self.pause()
556551
self.assertTrue(self.fs.exists(fake_temp_file.name))
557552
self.assertFalse(os.path.exists(fake_temp_file.name))
558-
self.real_temp_file = tempfile.NamedTemporaryFile()
559-
self.assertFalse(self.fs.exists(self.real_temp_file.name))
560-
self.assertTrue(os.path.exists(self.real_temp_file.name))
553+
real_temp_file = tempfile.NamedTemporaryFile()
554+
self.assertFalse(self.fs.exists(real_temp_file.name))
555+
self.assertTrue(os.path.exists(real_temp_file.name))
561556
self.resume()
562-
self.assertFalse(os.path.exists(self.real_temp_file.name))
557+
self.assertFalse(os.path.exists(real_temp_file.name))
563558
self.assertTrue(os.path.exists(fake_temp_file.name))
564559

565560
def test_pause_resume_fs(self):
@@ -572,15 +567,15 @@ def test_pause_resume_fs(self):
572567
self.fs.pause()
573568
self.assertTrue(self.fs.exists(fake_temp_file.name))
574569
self.assertFalse(os.path.exists(fake_temp_file.name))
575-
self.real_temp_file = tempfile.NamedTemporaryFile()
576-
self.assertFalse(self.fs.exists(self.real_temp_file.name))
577-
self.assertTrue(os.path.exists(self.real_temp_file.name))
570+
real_temp_file = tempfile.NamedTemporaryFile()
571+
self.assertFalse(self.fs.exists(real_temp_file.name))
572+
self.assertTrue(os.path.exists(real_temp_file.name))
578573
# pause does nothing if already paused
579574
self.fs.pause()
580-
self.assertFalse(self.fs.exists(self.real_temp_file.name))
581-
self.assertTrue(os.path.exists(self.real_temp_file.name))
575+
self.assertFalse(self.fs.exists(real_temp_file.name))
576+
self.assertTrue(os.path.exists(real_temp_file.name))
582577
self.fs.resume()
583-
self.assertFalse(os.path.exists(self.real_temp_file.name))
578+
self.assertFalse(os.path.exists(real_temp_file.name))
584579
self.assertTrue(os.path.exists(fake_temp_file.name))
585580

586581
def test_pause_resume_contextmanager(self):
@@ -590,10 +585,10 @@ def test_pause_resume_contextmanager(self):
590585
with Pause(self):
591586
self.assertTrue(self.fs.exists(fake_temp_file.name))
592587
self.assertFalse(os.path.exists(fake_temp_file.name))
593-
self.real_temp_file = tempfile.NamedTemporaryFile()
594-
self.assertFalse(self.fs.exists(self.real_temp_file.name))
595-
self.assertTrue(os.path.exists(self.real_temp_file.name))
596-
self.assertFalse(os.path.exists(self.real_temp_file.name))
588+
real_temp_file = tempfile.NamedTemporaryFile()
589+
self.assertFalse(self.fs.exists(real_temp_file.name))
590+
self.assertTrue(os.path.exists(real_temp_file.name))
591+
self.assertFalse(os.path.exists(real_temp_file.name))
597592
self.assertTrue(os.path.exists(fake_temp_file.name))
598593

599594
def test_pause_resume_fs_contextmanager(self):
@@ -603,10 +598,10 @@ def test_pause_resume_fs_contextmanager(self):
603598
with Pause(self.fs):
604599
self.assertTrue(self.fs.exists(fake_temp_file.name))
605600
self.assertFalse(os.path.exists(fake_temp_file.name))
606-
self.real_temp_file = tempfile.NamedTemporaryFile()
607-
self.assertFalse(self.fs.exists(self.real_temp_file.name))
608-
self.assertTrue(os.path.exists(self.real_temp_file.name))
609-
self.assertFalse(os.path.exists(self.real_temp_file.name))
601+
real_temp_file = tempfile.NamedTemporaryFile()
602+
self.assertFalse(self.fs.exists(real_temp_file.name))
603+
self.assertTrue(os.path.exists(real_temp_file.name))
604+
self.assertFalse(os.path.exists(real_temp_file.name))
610605
self.assertTrue(os.path.exists(fake_temp_file.name))
611606

612607
def test_pause_resume_without_patcher(self):

pyfakefs/tests/performance_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ class TimePerformanceTest(TestCase):
6161
"""
6262

6363
def test_cached_time(self):
64-
self.assertLess(SetupPerformanceTest.elapsed_time, 0.4)
64+
self.assertLess(SetupPerformanceTest.elapsed_time, 0.18)
6565

6666
def test_uncached_time(self):
67-
self.assertLess(SetupNoCachePerformanceTest.elapsed_time, 6)
67+
self.assertLess(SetupNoCachePerformanceTest.elapsed_time, 4)
6868

6969
def test_setup(self):
7070
pass

0 commit comments

Comments
 (0)