Skip to content

Commit b157fd2

Browse files
Added support for ns argument in os.utime
- fixes #192
1 parent e990e00 commit b157fd2

File tree

2 files changed

+83
-72
lines changed

2 files changed

+83
-72
lines changed

fake_filesystem_test.py

+25-43
Original file line numberDiff line numberDiff line change
@@ -681,28 +681,6 @@ def setUp(self):
681681
def tearDown(self):
682682
time.time = self.orig_time
683683

684-
def assertRaisesWithRegexpMatch(self, expected_exception, regexp_string,
685-
callable_obj, *args, **kwargs):
686-
"""Asserts that the message in a raised exception matches the given regexp.
687-
688-
Args:
689-
expected_exception: Exception class expected to be raised.
690-
regexp_string: Regexp (re pattern string) expected to be
691-
found in error message.
692-
callable_obj: Function to be called.
693-
*args: Extra args.
694-
**kwargs: Extra kwargs.
695-
"""
696-
try:
697-
callable_obj(*args, **kwargs)
698-
except expected_exception as err:
699-
expected_regexp = re.compile(regexp_string)
700-
self.assertTrue(
701-
expected_regexp.search(str(err)),
702-
'"%s" does not match "%s"' % (expected_regexp.pattern, str(err)))
703-
else:
704-
self.fail(expected_exception.__name__ + ' not raised')
705-
706684
def testChdir(self):
707685
"""chdir should work on a directory."""
708686
directory = '/foo'
@@ -1869,35 +1847,39 @@ def testUtimeNoFollowSymlinks(self):
18691847
self.assertEqual(2, st.st_mtime)
18701848

18711849
def testUtimeNonExistent(self):
1872-
# set up
18731850
path = '/non/existent/file'
18741851
self.assertFalse(self.filesystem.Exists(path))
1875-
# actual tests
1876-
try:
1877-
# Use try-catch to check exception attributes.
1878-
self.os.utime(path, (1, 2))
1879-
self.fail('Exception is expected.') # COV_NF_LINE
1880-
except OSError as os_error:
1881-
self.assertEqual(errno.ENOENT, os_error.errno)
1882-
self.assertEqual(path, os_error.filename)
1852+
self.assertRaisesOSError(errno.ENOENT, self.os.utime, path, (1, 2))
18831853

1884-
def testUtimeTupleArgIsOfIncorrectLength(self):
1885-
# set up
1854+
def testUtimeInvalidTimesArgRaises(self):
18861855
path = '/some_dir'
18871856
self._CreateTestDirectory(path)
1888-
# actual tests
1889-
self.assertRaisesWithRegexpMatch(
1890-
TypeError, r'utime\(\) arg 2 must be a tuple \(atime, mtime\)',
1891-
self.os.utime, path, (1, 2, 3))
18921857

1893-
def testUtimeTupleArgContainsIncorrectType(self):
1858+
# the error message differs with different Python versions
1859+
# we don't expect the same message here
1860+
self.assertRaises(TypeError, self.os.utime, path, (1, 2, 3))
1861+
self.assertRaises(TypeError, self.os.utime, path, (1, 'str'))
1862+
1863+
@unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3')
1864+
def testUtimeSetsSpecifiedTimeInNs(self):
18941865
# set up
1895-
path = '/some_dir'
1896-
self._CreateTestDirectory(path)
1866+
path = '/some_file'
1867+
self._CreateTestFile(path)
1868+
st = self.os.stat(path)
18971869
# actual tests
1898-
self.assertRaisesWithRegexpMatch(
1899-
TypeError, 'atime and mtime must be numbers',
1900-
self.os.utime, path, (1, 'str'))
1870+
self.os.utime(path, ns=(200000000, 400000000))
1871+
st = self.os.stat(path)
1872+
self.assertEqual(0.2, st.st_atime)
1873+
self.assertEqual(0.4, st.st_mtime)
1874+
1875+
@unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3')
1876+
def testUtimeIncorrectNsArgumentRaises(self):
1877+
file_path = 'some_file'
1878+
self.filesystem.CreateFile(file_path)
1879+
1880+
self.assertRaises(TypeError, self.os.utime, file_path, ns=(200000000))
1881+
self.assertRaises(TypeError, self.os.utime, file_path, ns=('a', 'b'))
1882+
self.assertRaises(ValueError, self.os.utime, file_path, times=(1, 2), ns=(100, 200))
19011883

19021884
def testChownExistingFile(self):
19031885
# set up

pyfakefs/fake_filesystem.py

+58-29
Original file line numberDiff line numberDiff line change
@@ -871,22 +871,36 @@ def ChangeMode(self, path, mode, follow_symlinks=True):
871871
(mode & PERM_ALL))
872872
file_object.st_ctime = time.time()
873873

874-
def UpdateTime(self, path, times, follow_symlinks=True):
874+
def UpdateTime(self, path, times=None, ns=None, follow_symlinks=True):
875875
"""Change the access and modified times of a file.
876876
New in pyfakefs 3.0.
877877
878878
Args:
879-
path: (str) Path to the file.
880-
times: 2-tuple of numbers, of the form (atime, mtime) which is used to set
881-
the access and modified times, respectively. If None, file's access
882-
and modified times are set to the current time.
883-
follow_symlinks: if False and entry_path points to a symlink, the link itself is queried
884-
instead of the linked object.
879+
path: (str) Path to the file.
880+
times: 2-tuple of int or float numbers, of the form (atime, mtime)
881+
which is used to set the access and modified times in seconds.
882+
If None, both times are set to the current time.
883+
ns: 2-tuple of int numbers, of the form (atime, mtime) which is
884+
used to set the access and modified times in nanoseconds.
885+
If None, both times are set to the current time.
886+
New in Python 3.3. New in pyfakefs 3.3.
887+
follow_symlinks: If `False` and entry_path points to a symlink,
888+
the link itself is queried instead of the linked object.
889+
New in Python 3.3. New in pyfakefs 3.0.
890+
891+
Raises:
892+
TypeError: If anything other than the expected types is
893+
specified in the passed `times` or `ns` tuple,
894+
or if the tuple length is not equal to 2.
895+
ValueError: If both times and ns are specified.
896+
"""
897+
if times is not None and ns is not None:
898+
raise ValueError("utime: you may specify either 'times' or 'ns' but not both")
899+
if times is not None and len(times) != 2:
900+
raise TypeError("utime: 'times' must be either a tuple of two ints or None")
901+
if ns is not None and len(ns) != 2:
902+
raise TypeError("utime: 'ns' must be a tuple of two ints")
885903

886-
Raises:
887-
TypeError: If anything other than integers is specified in passed tuple or
888-
number of elements in the tuple is not equal to 2.
889-
"""
890904
try:
891905
file_object = self.ResolveObject(path, follow_symlinks)
892906
except IOError as io_error:
@@ -895,18 +909,23 @@ def UpdateTime(self, path, times, follow_symlinks=True):
895909
'No such file or directory in fake filesystem',
896910
path)
897911
raise
898-
if times is None:
899-
file_object.st_atime = time.time()
900-
file_object.st_mtime = time.time()
901-
else:
902-
if len(times) != 2:
903-
raise TypeError('utime() arg 2 must be a tuple (atime, mtime)')
912+
if times is not None:
904913
for file_time in times:
905914
if not isinstance(file_time, (int, float)):
906915
raise TypeError('atime and mtime must be numbers')
907916

908917
file_object.st_atime = times[0]
909918
file_object.st_mtime = times[1]
919+
elif ns is not None:
920+
for file_time in ns:
921+
if not isinstance(file_time, int):
922+
raise TypeError('atime and mtime must be ints')
923+
924+
file_object.st_atime = ns[0] / 1e9
925+
file_object.st_mtime = ns[1] / 1e9
926+
else:
927+
file_object.st_atime = time.time()
928+
file_object.st_mtime = time.time()
910929

911930
def SetIno(self, path, st_ino):
912931
"""Set the self.st_ino attribute of file at 'path'.
@@ -3359,26 +3378,36 @@ def lchmod(self, path, mode):
33593378
raise (NameError, "name 'lchmod' is not defined")
33603379
self.filesystem.ChangeMode(path, mode, follow_symlinks=False)
33613380

3362-
def utime(self, path, times, follow_symlinks=None):
3381+
def utime(self, path, times=None, ns=None, follow_symlinks=None):
33633382
"""Change the access and modified times of a file.
33643383
33653384
Args:
3366-
path: (str) Path to the file.
3367-
times: 2-tuple of numbers, of the form (atime, mtime) which is used to set
3368-
the access and modified times, respectively. If None, file's access
3369-
and modified times are set to the current time.
3370-
follow_symlinks: if False and entry_path points to a symlink, the link itself is queried
3371-
instead of the linked object. New in Python 3.3. New in pyfakefs 3.0.
3372-
3373-
Raises:
3374-
TypeError: If anything other than integers is specified in passed tuple or
3375-
number of elements in the tuple is not equal to 2.
3385+
path: (str) Path to the file.
3386+
times: 2-tuple of int or float numbers, of the form (atime, mtime)
3387+
which is used to set the access and modified times in seconds.
3388+
If None, both times are set to the current time.
3389+
ns: 2-tuple of int numbers, of the form (atime, mtime) which is
3390+
used to set the access and modified times in nanoseconds.
3391+
If None, both times are set to the current time.
3392+
New in Python 3.3. New in pyfakefs 3.3.
3393+
follow_symlinks: If `False` and entry_path points to a symlink,
3394+
the link itself is queried instead of the linked object.
3395+
New in Python 3.3. New in pyfakefs 3.0.
3396+
3397+
Raises:
3398+
TypeError: If anything other than the expected types is
3399+
specified in the passed `times` or `ns` tuple,
3400+
or if the tuple length is not equal to 2.
3401+
ValueError: If both times and ns are specified.
33763402
"""
33773403
if follow_symlinks is None:
33783404
follow_symlinks = True
33793405
elif sys.version_info < (3, 3):
33803406
raise TypeError("utime() got an unexpected keyword argument 'follow_symlinks'")
3381-
self.filesystem.UpdateTime(path, times, follow_symlinks)
3407+
if ns is not None and sys.version_info < (3, 3):
3408+
raise TypeError("utime() got an unexpected keyword argument 'ns'")
3409+
3410+
self.filesystem.UpdateTime(path, times, ns, follow_symlinks)
33823411

33833412
def chown(self, path, uid, gid, follow_symlinks=None):
33843413
"""Set ownership of a faked file.

0 commit comments

Comments
 (0)