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,179 +326,6 @@ class StatAttributeTests(unittest.TestCase): ...@@ -336,179 +326,6 @@ 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):
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") @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
def test_1686475(self): def test_1686475(self):
# Verify that an open file can be stat'ed # Verify that an open file can be stat'ed
...@@ -533,6 +350,196 @@ class StatAttributeTests(unittest.TestCase): ...@@ -533,6 +350,196 @@ class StatAttributeTests(unittest.TestCase):
os.stat(r) os.stat(r)
self.assertEqual(ctx.exception.errno, errno.EBADF) 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 from test import mapping_tests
class EnvironTests(mapping_tests.BasicTestMappingProtocol): class EnvironTests(mapping_tests.BasicTestMappingProtocol):
...@@ -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