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
This commit is contained in:
parent
f5d4523844
commit
e12e7aa3fd
|
@ -2,32 +2,32 @@
|
|||
# does add tests for a few functions which have been determined to be more
|
||||
# portable than they had been thought to be.
|
||||
|
||||
import os
|
||||
import errno
|
||||
import unittest
|
||||
import warnings
|
||||
import sys
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
import shutil
|
||||
from test import support
|
||||
import asynchat
|
||||
import asyncore
|
||||
import codecs
|
||||
import contextlib
|
||||
import decimal
|
||||
import errno
|
||||
import fractions
|
||||
import itertools
|
||||
import locale
|
||||
import mmap
|
||||
import os
|
||||
import pickle
|
||||
import platform
|
||||
import re
|
||||
import uuid
|
||||
import asyncore
|
||||
import asynchat
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import itertools
|
||||
import stat
|
||||
import locale
|
||||
import codecs
|
||||
import decimal
|
||||
import fractions
|
||||
import pickle
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import time
|
||||
import unittest
|
||||
import uuid
|
||||
import warnings
|
||||
from test import support
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
|
@ -43,16 +43,6 @@ except ImportError:
|
|||
|
||||
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
|
||||
# and unmaintained) linuxthreads threading library. There's an issue
|
||||
# when combining linuxthreads with a failed execv call: see
|
||||
|
@ -336,179 +326,6 @@ class StatAttributeTests(unittest.TestCase):
|
|||
unpickled = pickle.loads(p)
|
||||
self.assertEqual(result, unpickled)
|
||||
|
||||
def test_utime_dir(self):
|
||||
delta = 1000000
|
||||
st = os.stat(support.TESTFN)
|
||||
# round to int, because some systems may support sub-second
|
||||
# time stamps in stat, but not in utime.
|
||||
os.utime(support.TESTFN, (st.st_atime, int(st.st_mtime-delta)))
|
||||
st2 = os.stat(support.TESTFN)
|
||||
self.assertEqual(st2.st_mtime, int(st.st_mtime-delta))
|
||||
|
||||
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):
|
||||
def utime(file, times):
|
||||
return os.utime(file, times)
|
||||
self._test_utime(self.fname, getattr, utime, 10)
|
||||
self._test_utime(support.TESTFN, getattr, utime, 10)
|
||||
|
||||
|
||||
def _test_utime_ns(self, set_times_ns, test_dir=True):
|
||||
def getattr_ns(o, attr):
|
||||
return getattr(o, attr + "_ns")
|
||||
ten_s = 10 * 1000 * 1000 * 1000
|
||||
self._test_utime(self.fname, getattr_ns, set_times_ns, ten_s)
|
||||
if test_dir:
|
||||
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)
|
||||
|
||||
|
||||
@unittest.skipUnless(stat_supports_subsecond,
|
||||
"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():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
os.stat_float_times(True)
|
||||
st = os.stat(filename)
|
||||
self.assertAlmostEqual(st.st_atime, atime, places=3)
|
||||
self.assertAlmostEqual(st.st_mtime, mtime, places=3)
|
||||
|
||||
def test_utime_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
os.utime(filename, (atime, mtime))
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
@requires_utime_fd
|
||||
def test_futimes_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
with open(filename, "wb") as f:
|
||||
os.utime(f.fileno(), times=(atime, mtime))
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
@requires_utime_fd
|
||||
def test_futimens_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
with open(filename, "wb") as f:
|
||||
os.utime(f.fileno(), times=(atime, mtime))
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
@requires_utime_dir_fd
|
||||
def test_futimesat_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
dirname = os.path.dirname(filename)
|
||||
dirfd = os.open(dirname, os.O_RDONLY)
|
||||
try:
|
||||
os.utime(os.path.basename(filename), dir_fd=dirfd,
|
||||
times=(atime, mtime))
|
||||
finally:
|
||||
os.close(dirfd)
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
@requires_utime_nofollow_symlinks
|
||||
def test_lutimes_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
os.utime(filename, (atime, mtime), follow_symlinks=False)
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
@requires_utime_dir_fd
|
||||
def test_utimensat_subsecond(self):
|
||||
def set_time(filename, atime, mtime):
|
||||
dirname = os.path.dirname(filename)
|
||||
dirfd = os.open(dirname, os.O_RDONLY)
|
||||
try:
|
||||
os.utime(os.path.basename(filename), dir_fd=dirfd,
|
||||
times=(atime, mtime))
|
||||
finally:
|
||||
os.close(dirfd)
|
||||
self._test_utime_subsecond(set_time)
|
||||
|
||||
# Restrict tests to Win32, since there is no guarantee other
|
||||
# systems support centiseconds
|
||||
def get_file_system(path):
|
||||
if sys.platform == 'win32':
|
||||
root = os.path.splitdrive(os.path.abspath(path))[0] + '\\'
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
buf = ctypes.create_unicode_buffer("", 100)
|
||||
if kernel32.GetVolumeInformationW(root, None, 0, None, None, None, buf, len(buf)):
|
||||
return buf.value
|
||||
|
||||
@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):
|
||||
t1 = 5000000000 # some day in 2128
|
||||
os.utime(self.fname, (t1, t1))
|
||||
self.assertEqual(os.stat(self.fname).st_mtime, t1)
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
||||
def test_1686475(self):
|
||||
# Verify that an open file can be stat'ed
|
||||
|
@ -533,6 +350,196 @@ class StatAttributeTests(unittest.TestCase):
|
|||
os.stat(r)
|
||||
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||||
|
||||
|
||||
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")
|
||||
|
||||
# ensure that st_atime and st_mtime are float
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
|
||||
old_state = os.stat_float_times(-1)
|
||||
self.addCleanup(os.stat_float_times, old_state)
|
||||
|
||||
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)
|
||||
return ((st.st_atime != st[7])
|
||||
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
|
||||
|
||||
support_subsecond = self.support_subsecond(filename)
|
||||
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))
|
||||
self._test_utime(set_time)
|
||||
|
||||
def test_utime_by_times(self):
|
||||
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 the times keyword parameter
|
||||
os.utime(filename, times=(atime, mtime))
|
||||
self._test_utime(set_time)
|
||||
|
||||
@unittest.skipUnless(os.utime in os.supports_follow_symlinks,
|
||||
"follow_symlinks support for utime required "
|
||||
"for this test.")
|
||||
def test_utime_nofollow_symlinks(self):
|
||||
def set_time(filename, ns):
|
||||
# use follow_symlinks=False to test utimensat(timespec)
|
||||
# or lutimes(timeval)
|
||||
os.utime(filename, ns=ns, follow_symlinks=False)
|
||||
self._test_utime(set_time)
|
||||
|
||||
@unittest.skipUnless(os.utime in os.supports_fd,
|
||||
"fd support for utime required for this test.")
|
||||
def test_utime_fd(self):
|
||||
def set_time(filename, ns):
|
||||
with open(filename, 'wb') as fp:
|
||||
# use a file descriptor to test futimens(timespec)
|
||||
# or futimes(timeval)
|
||||
os.utime(fp.fileno(), ns=ns)
|
||||
self._test_utime(set_time)
|
||||
|
||||
@unittest.skipUnless(os.utime in os.supports_dir_fd,
|
||||
"dir_fd support for utime required for this test.")
|
||||
def test_utime_dir_fd(self):
|
||||
def set_time(filename, ns):
|
||||
dirname, name = os.path.split(filename)
|
||||
dirfd = os.open(dirname, os.O_RDONLY)
|
||||
try:
|
||||
# pass dir_fd to test utimensat(timespec) or futimesat(timeval)
|
||||
os.utime(name, dir_fd=dirfd, ns=ns)
|
||||
finally:
|
||||
os.close(dirfd)
|
||||
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)
|
||||
|
||||
if not self.support_subsecond(self.fname):
|
||||
delta = 1.0
|
||||
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':
|
||||
root = os.path.splitdrive(os.path.abspath(path))[0] + '\\'
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
buf = ctypes.create_unicode_buffer("", 100)
|
||||
ok = kernel32.GetVolumeInformationW(root, None, 0,
|
||||
None, None, None,
|
||||
buf, len(buf))
|
||||
if ok:
|
||||
return buf.value
|
||||
# return None if the filesystem is unknown
|
||||
|
||||
def test_large_time(self):
|
||||
# Many filesystems are limited to the year 2038. At least, the test
|
||||
# pass with NTFS filesystem.
|
||||
if self.get_file_system(self.dirname) != "NTFS":
|
||||
self.skipTest("requires NTFS")
|
||||
|
||||
large = 5000000000 # some day in 2128
|
||||
os.utime(self.fname, (large, large))
|
||||
self.assertEqual(os.stat(self.fname).st_mtime, large)
|
||||
|
||||
def test_utime_invalid_arguments(self):
|
||||
# seconds and nanoseconds parameters are mutually exclusive
|
||||
with self.assertRaises(ValueError):
|
||||
os.utime(self.fname, (5, 5), ns=(5, 5))
|
||||
|
||||
|
||||
from test import mapping_tests
|
||||
|
||||
class EnvironTests(mapping_tests.BasicTestMappingProtocol):
|
||||
|
@ -2561,6 +2568,7 @@ def test_main():
|
|||
support.run_unittest(
|
||||
FileTests,
|
||||
StatAttributeTests,
|
||||
UtimeTests,
|
||||
EnvironTests,
|
||||
WalkTests,
|
||||
FwalkTests,
|
||||
|
|
Loading…
Reference in New Issue