From 9a88cbb5acd957b6f3aa783386a3372c4a631d22 Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Wed, 24 Oct 2018 18:53:00 +0200 Subject: [PATCH] Fixed recursion error on unpickling the fake file system - changed FakeStatResult so that pickling works under Python 2 - fixes #445 --- CHANGES.md | 2 ++ pyfakefs/fake_filesystem.py | 19 +++++++++---------- pyfakefs/helpers.py | 25 +++++++++++++++++-------- pyfakefs/tests/fake_filesystem_test.py | 9 +++++++++ 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8160f16c..3a16a008 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ The release versions are PyPi releases. #### New Features #### Fixes + * fixed recursion error on unpickling the fake file system + ([#445](../../issues/445)) ## [Version 3.5](https://pypi.python.org/pypi/pyfakefs/3.5) diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 1f1a94e4..d701d7df 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -288,35 +288,32 @@ def contents(self): @property def st_ctime(self): """Return the creation time of the fake file.""" - return (self._st_ctime if FakeOsModule.stat_float_times() - else int(self._st_ctime)) + return self.stat_result.st_ctime @property def st_atime(self): """Return the access time of the fake file.""" - return (self._st_atime if FakeOsModule.stat_float_times() - else int(self._st_atime)) + return self.stat_result.st_atime @property def st_mtime(self): """Return the modification time of the fake file.""" - return (self._st_mtime if FakeOsModule.stat_float_times() - else int(self._st_mtime)) + return self.stat_result.st_mtime @st_ctime.setter def st_ctime(self, val): """Set the creation time of the fake file.""" - self._st_ctime = val + self.stat_result.st_ctime = val @st_atime.setter def st_atime(self, val): """Set the access time of the fake file.""" - self._st_atime = val + self.stat_result.st_atime = val @st_mtime.setter def st_mtime(self, val): """Set the modification time of the fake file.""" - self._st_mtime = val + self.stat_result.st_mtime = val def set_large_file_size(self, st_size): """Sets the self.st_size attribute and replaces self.content with None. @@ -510,7 +507,9 @@ def SetCTime(self, st_ctime): def __getattr__(self, item): """Forward some properties to stat_result.""" - return getattr(self.stat_result, item) + if item in self.stat_types: + return getattr(self.stat_result, item) + return super(FakeFile, self).__getattr__(item) def __setattr__(self, key, value): """Forward some properties to stat_result.""" diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py index dd490a6d..7aa4b04f 100644 --- a/pyfakefs/helpers.py +++ b/pyfakefs/helpers.py @@ -71,7 +71,7 @@ class FakeStatResult(object): _stat_float_times = sys.version_info >= (2, 5) def __init__(self, is_windows, initial_time=None): - self.use_float = self.stat_float_times + self._use_float = None self.st_mode = None self.st_ino = None self.st_dev = None @@ -87,6 +87,16 @@ def __init__(self, is_windows, initial_time=None): self._st_mtime_ns = self._st_atime_ns self._st_ctime_ns = self._st_atime_ns + @property + def use_float(self): + if self._use_float is None: + return self.stat_float_times() + return self._use_float + + @use_float.setter + def use_float(self, val): + self._use_float = val + def __eq__(self, other): return ( isinstance(other, FakeStatResult) and @@ -106,12 +116,11 @@ def __ne__(self, other): return not self == other def copy(self): - """Return a copy where the float usage is hard-coded to mimic the behavior - of the real os.stat_result. + """Return a copy where the float usage is hard-coded to mimic the + behavior of the real os.stat_result. """ - use_float = self.use_float() stat_result = copy(self) - stat_result.use_float = lambda: use_float + stat_result.use_float = self.use_float return stat_result def set_from_stat_result(self, stat_result): @@ -152,19 +161,19 @@ def stat_float_times(cls, newvalue=None): def st_ctime(self): """Return the creation time in seconds.""" ctime = self._st_ctime_ns / 1e9 - return ctime if self.use_float() else int(ctime) + return ctime if self.use_float else int(ctime) @property def st_atime(self): """Return the access time in seconds.""" atime = self._st_atime_ns / 1e9 - return atime if self.use_float() else int(atime) + return atime if self.use_float else int(atime) @property def st_mtime(self): """Return the modification time in seconds.""" mtime = self._st_mtime_ns / 1e9 - return mtime if self.use_float() else int(mtime) + return mtime if self.use_float else int(mtime) @st_ctime.setter def st_ctime(self, val): diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py index c043700c..234ed3fb 100644 --- a/pyfakefs/tests/fake_filesystem_test.py +++ b/pyfakefs/tests/fake_filesystem_test.py @@ -633,6 +633,15 @@ def test_directory_access_on_file_posix(self): self.filesystem.is_windows_fs = False self.check_directory_access_on_file(errno.ENOTDIR) + def test_pickle_fs(self): + """Regression test for #445""" + import pickle + self.filesystem.open_files = [] + p = pickle.dumps(self.filesystem) + fs = pickle.loads(p) + self.assertEqual(str(fs.root), str(self.filesystem.root)) + self.assertEqual(fs.mount_points, self.filesystem.mount_points) + class CaseInsensitiveFakeFilesystemTest(TestCase): def setUp(self):