Skip to content

Commit

Permalink
Adapted file timestamp update to real fs behavior
Browse files Browse the repository at this point in the history
- tests originally by @simonfagerholm, adapted
- fixes #435
  • Loading branch information
mrbean-bremen committed Oct 8, 2018
1 parent 1465cc4 commit 1886f92
Show file tree
Hide file tree
Showing 6 changed files with 641 additions and 64 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -19,6 +19,8 @@ keep using pyfakefs 3.4.3, or upgrade to a newer Python version.
#### Infrastructure

#### Fixes
* file timestamps are now updated more according to the real behavior
([#435](../../issues/435))

## [Version 3.4.3](https://pypi.python.org/pypi/pyfakefs/3.4.3)

Expand Down
35 changes: 28 additions & 7 deletions pyfakefs/fake_filesystem.py
Expand Up @@ -341,12 +341,17 @@ def _set_initial_contents(self, contents):
Called internally after initial file creation.
Args:
contents: string, new content of file.
contents: string, new content of file.
Returns:
True if the contents have been changed.
Raises:
IOError: if the st_size is not a non-negative integer,
IOError: if the st_size is not a non-negative integer,
or if st_size exceeds the available file system space
"""
contents = self._encode_contents(contents)
changed = self._byte_contents != contents
st_size = len(contents)

if self._byte_contents:
Expand All @@ -357,6 +362,7 @@ def _set_initial_contents(self, contents):
self._byte_contents = contents
self.st_size = st_size
self.epoch += 1
return changed

def set_contents(self, contents, encoding=None):
"""Sets the file contents and size and increases the modification time.
Expand All @@ -373,12 +379,10 @@ def set_contents(self, contents, encoding=None):
or if it exceeds the available file system space.
"""
self.encoding = encoding
self._set_initial_contents(contents)
current_time = time.time()
self.st_ctime = current_time
self.st_mtime = current_time
changed = self._set_initial_contents(contents)
if self._side_effect is not None:
self._side_effect(self)
return changed

@property
def size(self):
Expand Down Expand Up @@ -4450,6 +4454,7 @@ def __init__(self, file_object, file_path, update=False, read=False,
self.raw_io = raw_io
self._binary = binary
self.is_stream = is_stream
self._changed = False
contents = file_object.byte_contents
self._encoding = encoding or locale.getpreferredencoding(False)
errors = errors or 'strict'
Expand Down Expand Up @@ -4514,6 +4519,8 @@ def close(self):
# for raw io, all writes are flushed immediately
if self.allow_update and not self.raw_io:
self.flush()
if self._filesystem.is_windows_fs and self._changed:
self.file_object.st_mtime = time.time()
if self._closefd:
self._filesystem._close_open_file(self.filedes)
else:
Expand Down Expand Up @@ -4541,7 +4548,13 @@ def flush(self):
self.update_flush_pos()
else:
self._io.flush()
self.file_object.set_contents(contents, self._encoding)
if self.file_object.set_contents(contents, self._encoding):
if self._filesystem.is_windows_fs:
self._changed = True
else:
current_time = time.time()
self.file_object.st_ctime = current_time
self.file_object.st_mtime = current_time
self._file_epoch = self.file_object.epoch

if not self.is_stream:
Expand Down Expand Up @@ -4772,6 +4785,8 @@ def __getattr__(self, name):
self._sync_io()
if self._flushes_after_read():
self.flush()
if not self._filesystem.is_windows_fs:
self.file_object.st_atime = time.time()
if truncate:
return self._truncate_wrapper()
if self._append:
Expand Down Expand Up @@ -4992,6 +5007,12 @@ def call(self, file_, mode='r', buffering=-1, encoding=None,
# If you print obj.name, the argument to open() must be printed.
# Not the abspath, not the filename, but the actual argument.
file_object.opened_as = file_path
if open_modes.truncate:
current_time = time.time()
file_object.st_mtime = current_time
if not self.filesystem.is_windows_fs:
file_object.st_ctime = current_time


fakefile = FakeFileWrapper(file_object,
file_path,
Expand Down
3 changes: 2 additions & 1 deletion pyfakefs/tests/all_tests.py
Expand Up @@ -20,7 +20,7 @@
import unittest

from pyfakefs.extra_packages import pathlib
from pyfakefs.tests import dynamic_patch_test
from pyfakefs.tests import dynamic_patch_test, fake_stat_time_test
from pyfakefs.tests import fake_open_test
from pyfakefs.tests import fake_os_test
from pyfakefs.tests import example_test
Expand All @@ -46,6 +46,7 @@ def suite(self): # pylint: disable-msg=C6409
loader.loadTestsFromModule(fake_filesystem_glob_test),
loader.loadTestsFromModule(fake_filesystem_shutil_test),
loader.loadTestsFromModule(fake_os_test),
loader.loadTestsFromModule(fake_stat_time_test),
loader.loadTestsFromModule(fake_open_test),
loader.loadTestsFromModule(fake_tempfile_test),
loader.loadTestsFromModule(fake_filesystem_vs_real_test),
Expand Down
55 changes: 0 additions & 55 deletions pyfakefs/tests/fake_open_test.py
Expand Up @@ -424,61 +424,6 @@ def test_read_with_rplus(self):
fake_file.seek(0)
self.assertEqual('new contents here', fake_file.read())

def test_open_st_ctime(self):
# set up
self.skip_real_fs()
time.time = DummyTime(100, 10)
file_path = self.make_path('some_file')
self.assertFalse(self.os.path.exists(file_path))
# tests
fake_file = self.open(file_path, 'w')
time.time.start()
st = self.os.stat(file_path)
self.assertEqual(100, st.st_ctime)
self.assertEqual(100, st.st_mtime)
fake_file.close()
st = self.os.stat(file_path)
self.assertEqual(110, st.st_ctime)
self.assertEqual(110, st.st_mtime)

fake_file = self.open(file_path, 'w')
st = self.os.stat(file_path)
# truncating the file cause an additional stat update
self.assertEqual(120, st.st_ctime)
self.assertEqual(120, st.st_mtime)
fake_file.close()
st = self.os.stat(file_path)
self.assertEqual(130, st.st_ctime)
self.assertEqual(130, st.st_mtime)

fake_file = self.open(file_path, 'w+')
st = self.os.stat(file_path)
self.assertEqual(140, st.st_ctime)
self.assertEqual(140, st.st_mtime)
fake_file.close()
st = self.os.stat(file_path)
self.assertEqual(150, st.st_ctime)
self.assertEqual(150, st.st_mtime)

fake_file = self.open(file_path, 'a')
st = self.os.stat(file_path)
# not updating m_time or c_time here, since no truncating.
self.assertEqual(150, st.st_ctime)
self.assertEqual(150, st.st_mtime)
fake_file.close()
st = self.os.stat(file_path)
self.assertEqual(160, st.st_ctime)
self.assertEqual(160, st.st_mtime)

fake_file = self.open(file_path, 'r')
st = self.os.stat(file_path)
self.assertEqual(160, st.st_ctime)
self.assertEqual(160, st.st_mtime)
fake_file.close()
st = self.os.stat(file_path)
self.assertEqual(160, st.st_ctime)
self.assertEqual(160, st.st_mtime)

def create_with_permission(self, file_path, perm_bits):
self.create_file(file_path)
self.os.chmod(file_path, perm_bits)
Expand Down

0 comments on commit 1886f92

Please sign in to comment.