Commit 79755adb authored by Victor Stinner's avatar Victor Stinner

Issue #15745: Rewrite os.utime() tests in test_os

* Don't use the timestamp of an existing file anymore, only use fixed
  timestamp
* Enhance the code checking the resolution of the filesystem timestamps.
* Check timestamps with a resolution of 1 microsecond instead of 1 millisecond
* When os.utime() uses the current system clock, tolerate a delta of 20 ms.
  Before some os.utime() tolerated a different of 10 seconds.
* Merge duplicated tests and simplify the code
parent 5ce218e7
...@@ -2,32 +2,32 @@ ...@@ -2,32 +2,32 @@
# does add tests for a few functions which have been determined to be more # does add tests for a few functions which have been determined to be more
# portable than they had been thought to be. # portable than they had been thought to be.
import os import asynchat
import errno import asyncore
import unittest import codecs
import warnings
import sys
import signal
import subprocess
import time
import shutil
from test import support
import contextlib import contextlib
import decimal
import errno
import fractions
import itertools
import locale
import mmap import mmap
import os
import pickle
import platform import platform
import re import re
import uuid import shutil
import asyncore import signal
import asynchat
import socket import socket
import itertools
import stat import stat
import locale import subprocess
import codecs import sys
import decimal
import fractions
import pickle
import sysconfig import sysconfig
import time
import unittest
import uuid
import warnings
from test import support
try: try:
import threading import threading
except ImportError: except ImportError:
...@@ -43,16 +43,6 @@ except ImportError: ...@@ -43,16 +43,6 @@ except ImportError:
from test.script_helper import assert_python_ok from test.script_helper import assert_python_ok
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
os.stat_float_times(True)
st = os.stat(__file__)
stat_supports_subsecond = (
# check if float and int timestamps are different
(st.st_atime != st[7])
or (st.st_mtime != st[8])
or (st.st_ctime != st[9]))
# Detect whether we're on a Linux system that uses the (now outdated # Detect whether we're on a Linux system that uses the (now outdated
# and unmaintained) linuxthreads threading library. There's an issue # and unmaintained) linuxthreads threading library. There's an issue
# when combining linuxthreads with a failed execv call: see # when combining linuxthreads with a failed execv call: see
...@@ -336,202 +326,219 @@ class StatAttributeTests(unittest.TestCase): ...@@ -336,202 +326,219 @@ class StatAttributeTests(unittest.TestCase):
unpickled = pickle.loads(p) unpickled = pickle.loads(p)
self.assertEqual(result, unpickled) self.assertEqual(result, unpickled)
def test_utime_dir(self): @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
delta = 1000000 def test_1686475(self):
st = os.stat(support.TESTFN) # Verify that an open file can be stat'ed
# round to int, because some systems may support sub-second try:
# time stamps in stat, but not in utime. os.stat(r"c:\pagefile.sys")
os.utime(support.TESTFN, (st.st_atime, int(st.st_mtime-delta))) except FileNotFoundError:
st2 = os.stat(support.TESTFN) self.skipTest(r'c:\pagefile.sys does not exist')
self.assertEqual(st2.st_mtime, int(st.st_mtime-delta)) except OSError as e:
self.fail("Could not stat pagefile.sys")
def _test_utime(self, filename, attr, utime, delta):
# Issue #13327 removed the requirement to pass None as the
# second argument. Check that the previous methods of passing
# a time tuple or None work in addition to no argument.
st0 = os.stat(filename)
# Doesn't set anything new, but sets the time tuple way
utime(filename, (attr(st0, "st_atime"), attr(st0, "st_mtime")))
# Setting the time to the time you just read, then reading again,
# should always return exactly the same times.
st1 = os.stat(filename)
self.assertEqual(attr(st0, "st_mtime"), attr(st1, "st_mtime"))
self.assertEqual(attr(st0, "st_atime"), attr(st1, "st_atime"))
# Set to the current time in the old explicit way.
os.utime(filename, None)
st2 = os.stat(support.TESTFN)
# Set to the current time in the new way
os.utime(filename)
st3 = os.stat(filename)
self.assertAlmostEqual(attr(st2, "st_mtime"), attr(st3, "st_mtime"), delta=delta)
def test_utime(self): @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
def utime(file, times): @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
return os.utime(file, times) def test_15261(self):
self._test_utime(self.fname, getattr, utime, 10) # Verify that stat'ing a closed fd does not cause crash
self._test_utime(support.TESTFN, getattr, utime, 10) r, w = os.pipe()
try:
os.stat(r) # should not raise error
def _test_utime_ns(self, set_times_ns, test_dir=True): finally:
def getattr_ns(o, attr): os.close(r)
return getattr(o, attr + "_ns") os.close(w)
ten_s = 10 * 1000 * 1000 * 1000 with self.assertRaises(OSError) as ctx:
self._test_utime(self.fname, getattr_ns, set_times_ns, ten_s) os.stat(r)
if test_dir: self.assertEqual(ctx.exception.errno, errno.EBADF)
self._test_utime(support.TESTFN, getattr_ns, set_times_ns, ten_s)
def test_utime_ns(self):
def utime_ns(file, times):
return os.utime(file, ns=times)
self._test_utime_ns(utime_ns)
requires_utime_dir_fd = unittest.skipUnless(
os.utime in os.supports_dir_fd,
"dir_fd support for utime required for this test.")
requires_utime_fd = unittest.skipUnless(
os.utime in os.supports_fd,
"fd support for utime required for this test.")
requires_utime_nofollow_symlinks = unittest.skipUnless(
os.utime in os.supports_follow_symlinks,
"follow_symlinks support for utime required for this test.")
@requires_utime_nofollow_symlinks
def test_lutimes_ns(self):
def lutimes_ns(file, times):
return os.utime(file, ns=times, follow_symlinks=False)
self._test_utime_ns(lutimes_ns)
@requires_utime_fd
def test_futimes_ns(self):
def futimes_ns(file, times):
with open(file, "wb") as f:
os.utime(f.fileno(), ns=times)
self._test_utime_ns(futimes_ns, test_dir=False)
def _utime_invalid_arguments(self, name, arg):
with self.assertRaises(ValueError):
getattr(os, name)(arg, (5, 5), ns=(5, 5))
def test_utime_invalid_arguments(self):
self._utime_invalid_arguments('utime', self.fname)
class UtimeTests(unittest.TestCase):
def setUp(self):
self.dirname = support.TESTFN
self.fname = os.path.join(self.dirname, "f1")
self.addCleanup(support.rmtree, self.dirname)
os.mkdir(self.dirname)
with open(self.fname, 'wb') as fp:
fp.write(b"ABC")
@unittest.skipUnless(stat_supports_subsecond, # ensure that st_atime and st_mtime are float
"os.stat() doesn't has a subsecond resolution")
def _test_utime_subsecond(self, set_time_func):
asec, amsec = 1, 901
atime = asec + amsec * 1e-3
msec, mmsec = 2, 901
mtime = msec + mmsec * 1e-3
filename = self.fname
os.utime(filename, (0, 0))
set_time_func(filename, atime, mtime)
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning) warnings.simplefilter("ignore", DeprecationWarning)
old_state = os.stat_float_times(-1)
self.addCleanup(os.stat_float_times, old_state)
os.stat_float_times(True) os.stat_float_times(True)
def support_subsecond(self, filename):
# Heuristic to check if the filesystem supports timestamp with
# subsecond resolution: check if float and int timestamps are different
st = os.stat(filename) st = os.stat(filename)
self.assertAlmostEqual(st.st_atime, atime, places=3) return ((st.st_atime != st[7])
self.assertAlmostEqual(st.st_mtime, mtime, places=3) or (st.st_mtime != st[8])
or (st.st_ctime != st[9]))
def _test_utime(self, set_time, filename=None):
if not filename:
filename = self.fname
def test_utime_subsecond(self): support_subsecond = self.support_subsecond(filename)
def set_time(filename, atime, mtime): if support_subsecond:
# Timestamp with a resolution of 1 microsecond (10^-6).
#
# The resolution of the C internal function used by os.utime()
# depends on the platform: 1 sec, 1 us, 1 ns. Writing a portable
# test with a resolution of 1 ns requires more work:
# see the issue #15745.
atime_ns = 1002003000 # 1.002003 seconds
mtime_ns = 4005006000 # 4.005006 seconds
else:
# use a resolution of 1 second
atime_ns = 5 * 10**9
mtime_ns = 8 * 10**9
set_time(filename, (atime_ns, mtime_ns))
st = os.stat(filename)
if support_subsecond:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
else:
self.assertEqual(st.st_atime, atime_ns * 1e-9)
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
self.assertEqual(st.st_atime_ns, atime_ns)
self.assertEqual(st.st_mtime_ns, mtime_ns)
def test_utime(self):
def set_time(filename, ns):
# test the ns keyword parameter
os.utime(filename, ns=ns)
self._test_utime(set_time)
@staticmethod
def ns_to_sec(ns):
# Convert a number of nanosecond (int) to a number of seconds (float).
# Round towards infinity by adding 0.5 nanosecond to avoid rounding
# issue, os.utime() rounds towards minus infinity.
return (ns * 1e-9) + 0.5e-9
def test_utime_by_indexed(self):
# pass times as floating point seconds as the second indexed parameter
def set_time(filename, ns):
atime_ns, mtime_ns = ns
atime = self.ns_to_sec(atime_ns)
mtime = self.ns_to_sec(mtime_ns)
# test utimensat(timespec), utimes(timeval), utime(utimbuf)
# or utime(time_t)
os.utime(filename, (atime, mtime)) os.utime(filename, (atime, mtime))
self._test_utime_subsecond(set_time) self._test_utime(set_time)
@requires_utime_fd def test_utime_by_times(self):
def test_futimes_subsecond(self): def set_time(filename, ns):
def set_time(filename, atime, mtime): atime_ns, mtime_ns = ns
with open(filename, "wb") as f: atime = self.ns_to_sec(atime_ns)
os.utime(f.fileno(), times=(atime, mtime)) mtime = self.ns_to_sec(mtime_ns)
self._test_utime_subsecond(set_time) # test the times keyword parameter
os.utime(filename, times=(atime, mtime))
@requires_utime_fd self._test_utime(set_time)
def test_futimens_subsecond(self):
def set_time(filename, atime, mtime): @unittest.skipUnless(os.utime in os.supports_follow_symlinks,
with open(filename, "wb") as f: "follow_symlinks support for utime required "
os.utime(f.fileno(), times=(atime, mtime)) "for this test.")
self._test_utime_subsecond(set_time) def test_utime_nofollow_symlinks(self):
def set_time(filename, ns):
@requires_utime_dir_fd # use follow_symlinks=False to test utimensat(timespec)
def test_futimesat_subsecond(self): # or lutimes(timeval)
def set_time(filename, atime, mtime): os.utime(filename, ns=ns, follow_symlinks=False)
dirname = os.path.dirname(filename) self._test_utime(set_time)
dirfd = os.open(dirname, os.O_RDONLY)
try: @unittest.skipUnless(os.utime in os.supports_fd,
os.utime(os.path.basename(filename), dir_fd=dirfd, "fd support for utime required for this test.")
times=(atime, mtime)) def test_utime_fd(self):
finally: def set_time(filename, ns):
os.close(dirfd) with open(filename, 'wb') as fp:
self._test_utime_subsecond(set_time) # use a file descriptor to test futimens(timespec)
# or futimes(timeval)
@requires_utime_nofollow_symlinks os.utime(fp.fileno(), ns=ns)
def test_lutimes_subsecond(self): self._test_utime(set_time)
def set_time(filename, atime, mtime):
os.utime(filename, (atime, mtime), follow_symlinks=False) @unittest.skipUnless(os.utime in os.supports_dir_fd,
self._test_utime_subsecond(set_time) "dir_fd support for utime required for this test.")
def test_utime_dir_fd(self):
@requires_utime_dir_fd def set_time(filename, ns):
def test_utimensat_subsecond(self): dirname, name = os.path.split(filename)
def set_time(filename, atime, mtime):
dirname = os.path.dirname(filename)
dirfd = os.open(dirname, os.O_RDONLY) dirfd = os.open(dirname, os.O_RDONLY)
try: try:
os.utime(os.path.basename(filename), dir_fd=dirfd, # pass dir_fd to test utimensat(timespec) or futimesat(timeval)
times=(atime, mtime)) os.utime(name, dir_fd=dirfd, ns=ns)
finally: finally:
os.close(dirfd) os.close(dirfd)
self._test_utime_subsecond(set_time) self._test_utime(set_time)
def test_utime_directory(self):
def set_time(filename, ns):
# test calling os.utime() on a directory
os.utime(filename, ns=ns)
self._test_utime(set_time, filename=self.dirname)
def _test_utime_current(self, set_time):
# Get the system clock
current = time.time()
# Call os.utime() to set the timestamp to the current system clock
set_time(self.fname)
# Restrict tests to Win32, since there is no guarantee other if not self.support_subsecond(self.fname):
# systems support centiseconds delta = 1.0
def get_file_system(path): else:
# On Windows, the usual resolution of time.time() is 15.6 ms
delta = 0.020
st = os.stat(self.fname)
msg = ("st_time=%r, current=%r, dt=%r"
% (st.st_mtime, current, st.st_mtime - current))
self.assertAlmostEqual(st.st_mtime, current,
delta=delta, msg=msg)
def test_utime_current(self):
def set_time(filename):
# Set to the current time in the new way
os.utime(self.fname)
self._test_utime_current(set_time)
def test_utime_current_old(self):
def set_time(filename):
# Set to the current time in the old explicit way.
os.utime(self.fname, None)
self._test_utime_current(set_time)
def get_file_system(self, path):
if sys.platform == 'win32': if sys.platform == 'win32':
root = os.path.splitdrive(os.path.abspath(path))[0] + '\\' root = os.path.splitdrive(os.path.abspath(path))[0] + '\\'
import ctypes import ctypes
kernel32 = ctypes.windll.kernel32 kernel32 = ctypes.windll.kernel32
buf = ctypes.create_unicode_buffer("", 100) buf = ctypes.create_unicode_buffer("", 100)
if kernel32.GetVolumeInformationW(root, None, 0, None, None, None, buf, len(buf)): ok = kernel32.GetVolumeInformationW(root, None, 0,
None, None, None,
buf, len(buf))
if ok:
return buf.value return buf.value
# return None if the filesystem is unknown
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS",
"requires NTFS")
def test_1565150(self):
t1 = 1159195039.25
os.utime(self.fname, (t1, t1))
self.assertEqual(os.stat(self.fname).st_mtime, t1)
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@unittest.skipUnless(get_file_system(support.TESTFN) == "NTFS",
"requires NTFS")
def test_large_time(self): def test_large_time(self):
t1 = 5000000000 # some day in 2128 # Many filesystems are limited to the year 2038. At least, the test
os.utime(self.fname, (t1, t1)) # pass with NTFS filesystem.
self.assertEqual(os.stat(self.fname).st_mtime, t1) if self.get_file_system(self.dirname) != "NTFS":
self.skipTest("requires NTFS")
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") large = 5000000000 # some day in 2128
def test_1686475(self): os.utime(self.fname, (large, large))
# Verify that an open file can be stat'ed self.assertEqual(os.stat(self.fname).st_mtime, large)
try:
os.stat(r"c:\pagefile.sys") def test_utime_invalid_arguments(self):
except FileNotFoundError: # seconds and nanoseconds parameters are mutually exclusive
self.skipTest(r'c:\pagefile.sys does not exist') with self.assertRaises(ValueError):
except OSError as e: os.utime(self.fname, (5, 5), ns=(5, 5))
self.fail("Could not stat pagefile.sys")
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_15261(self):
# Verify that stat'ing a closed fd does not cause crash
r, w = os.pipe()
try:
os.stat(r) # should not raise error
finally:
os.close(r)
os.close(w)
with self.assertRaises(OSError) as ctx:
os.stat(r)
self.assertEqual(ctx.exception.errno, errno.EBADF)
from test import mapping_tests from test import mapping_tests
...@@ -2561,6 +2568,7 @@ def test_main(): ...@@ -2561,6 +2568,7 @@ def test_main():
support.run_unittest( support.run_unittest(
FileTests, FileTests,
StatAttributeTests, StatAttributeTests,
UtimeTests,
EnvironTests, EnvironTests,
WalkTests, WalkTests,
FwalkTests, FwalkTests,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment