mirror of https://github.com/python/cpython
5389 lines
199 KiB
Python
5389 lines
199 KiB
Python
# As a test suite for the os module, this is woefully inadequate, but this
|
|
# does add tests for a few functions which have been determined to be more
|
|
# portable than they had been thought to be.
|
|
|
|
import asyncio
|
|
import codecs
|
|
import contextlib
|
|
import decimal
|
|
import errno
|
|
import fnmatch
|
|
import fractions
|
|
import itertools
|
|
import locale
|
|
import os
|
|
import pickle
|
|
import select
|
|
import selectors
|
|
import shutil
|
|
import signal
|
|
import socket
|
|
import stat
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import sysconfig
|
|
import tempfile
|
|
import textwrap
|
|
import time
|
|
import types
|
|
import unittest
|
|
import uuid
|
|
import warnings
|
|
from test import support
|
|
from test.support import import_helper
|
|
from test.support import os_helper
|
|
from test.support import socket_helper
|
|
from test.support import infinite_recursion
|
|
from test.support import warnings_helper
|
|
from platform import win32_is_iot
|
|
|
|
try:
|
|
import resource
|
|
except ImportError:
|
|
resource = None
|
|
try:
|
|
import fcntl
|
|
except ImportError:
|
|
fcntl = None
|
|
try:
|
|
import _winapi
|
|
except ImportError:
|
|
_winapi = None
|
|
try:
|
|
import pwd
|
|
all_users = [u.pw_uid for u in pwd.getpwall()]
|
|
except (ImportError, AttributeError):
|
|
all_users = []
|
|
try:
|
|
from _testcapi import INT_MAX, PY_SSIZE_T_MAX
|
|
except ImportError:
|
|
INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
|
|
|
|
try:
|
|
import mmap
|
|
except ImportError:
|
|
mmap = None
|
|
|
|
from test.support.script_helper import assert_python_ok
|
|
from test.support import unix_shell
|
|
from test.support.os_helper import FakePath
|
|
|
|
|
|
root_in_posix = False
|
|
if hasattr(os, 'geteuid'):
|
|
root_in_posix = (os.geteuid() == 0)
|
|
|
|
# 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
|
|
# http://bugs.python.org/issue4970.
|
|
if hasattr(sys, 'thread_info') and sys.thread_info.version:
|
|
USING_LINUXTHREADS = sys.thread_info.version.startswith("linuxthreads")
|
|
else:
|
|
USING_LINUXTHREADS = False
|
|
|
|
# Issue #14110: Some tests fail on FreeBSD if the user is in the wheel group.
|
|
HAVE_WHEEL_GROUP = sys.platform.startswith('freebsd') and os.getgid() == 0
|
|
|
|
|
|
def requires_os_func(name):
|
|
return unittest.skipUnless(hasattr(os, name), 'requires os.%s' % name)
|
|
|
|
|
|
def create_file(filename, content=b'content'):
|
|
with open(filename, "xb", 0) as fp:
|
|
fp.write(content)
|
|
|
|
|
|
# bpo-41625: On AIX, splice() only works with a socket, not with a pipe.
|
|
requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"),
|
|
'on AIX, splice() only accepts sockets')
|
|
|
|
|
|
def tearDownModule():
|
|
asyncio.set_event_loop_policy(None)
|
|
|
|
|
|
class MiscTests(unittest.TestCase):
|
|
def test_getcwd(self):
|
|
cwd = os.getcwd()
|
|
self.assertIsInstance(cwd, str)
|
|
|
|
def test_getcwd_long_path(self):
|
|
# bpo-37412: On Linux, PATH_MAX is usually around 4096 bytes. On
|
|
# Windows, MAX_PATH is defined as 260 characters, but Windows supports
|
|
# longer path if longer paths support is enabled. Internally, the os
|
|
# module uses MAXPATHLEN which is at least 1024.
|
|
#
|
|
# Use a directory name of 200 characters to fit into Windows MAX_PATH
|
|
# limit.
|
|
#
|
|
# On Windows, the test can stop when trying to create a path longer
|
|
# than MAX_PATH if long paths support is disabled:
|
|
# see RtlAreLongPathsEnabled().
|
|
min_len = 2000 # characters
|
|
# On VxWorks, PATH_MAX is defined as 1024 bytes. Creating a path
|
|
# longer than PATH_MAX will fail.
|
|
if sys.platform == 'vxworks':
|
|
min_len = 1000
|
|
dirlen = 200 # characters
|
|
dirname = 'python_test_dir_'
|
|
dirname = dirname + ('a' * (dirlen - len(dirname)))
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
with os_helper.change_cwd(tmpdir) as path:
|
|
expected = path
|
|
|
|
while True:
|
|
cwd = os.getcwd()
|
|
self.assertEqual(cwd, expected)
|
|
|
|
need = min_len - (len(cwd) + len(os.path.sep))
|
|
if need <= 0:
|
|
break
|
|
if len(dirname) > need and need > 0:
|
|
dirname = dirname[:need]
|
|
|
|
path = os.path.join(path, dirname)
|
|
try:
|
|
os.mkdir(path)
|
|
# On Windows, chdir() can fail
|
|
# even if mkdir() succeeded
|
|
os.chdir(path)
|
|
except FileNotFoundError:
|
|
# On Windows, catch ERROR_PATH_NOT_FOUND (3) and
|
|
# ERROR_FILENAME_EXCED_RANGE (206) errors
|
|
# ("The filename or extension is too long")
|
|
break
|
|
except OSError as exc:
|
|
if exc.errno == errno.ENAMETOOLONG:
|
|
break
|
|
else:
|
|
raise
|
|
|
|
expected = path
|
|
|
|
if support.verbose:
|
|
print(f"Tested current directory length: {len(cwd)}")
|
|
|
|
def test_getcwdb(self):
|
|
cwd = os.getcwdb()
|
|
self.assertIsInstance(cwd, bytes)
|
|
self.assertEqual(os.fsdecode(cwd), os.getcwd())
|
|
|
|
|
|
# Tests creating TESTFN
|
|
class FileTests(unittest.TestCase):
|
|
def setUp(self):
|
|
if os.path.lexists(os_helper.TESTFN):
|
|
os.unlink(os_helper.TESTFN)
|
|
tearDown = setUp
|
|
|
|
def test_access(self):
|
|
f = os.open(os_helper.TESTFN, os.O_CREAT|os.O_RDWR)
|
|
os.close(f)
|
|
self.assertTrue(os.access(os_helper.TESTFN, os.W_OK))
|
|
|
|
@unittest.skipIf(
|
|
support.is_emscripten, "Test is unstable under Emscripten."
|
|
)
|
|
@unittest.skipIf(
|
|
support.is_wasi, "WASI does not support dup."
|
|
)
|
|
def test_closerange(self):
|
|
first = os.open(os_helper.TESTFN, os.O_CREAT|os.O_RDWR)
|
|
# We must allocate two consecutive file descriptors, otherwise
|
|
# it will mess up other file descriptors (perhaps even the three
|
|
# standard ones).
|
|
second = os.dup(first)
|
|
try:
|
|
retries = 0
|
|
while second != first + 1:
|
|
os.close(first)
|
|
retries += 1
|
|
if retries > 10:
|
|
# XXX test skipped
|
|
self.skipTest("couldn't allocate two consecutive fds")
|
|
first, second = second, os.dup(second)
|
|
finally:
|
|
os.close(second)
|
|
# close a fd that is open, and one that isn't
|
|
os.closerange(first, first + 2)
|
|
self.assertRaises(OSError, os.write, first, b"a")
|
|
|
|
@support.cpython_only
|
|
def test_rename(self):
|
|
path = os_helper.TESTFN
|
|
old = sys.getrefcount(path)
|
|
self.assertRaises(TypeError, os.rename, path, 0)
|
|
new = sys.getrefcount(path)
|
|
self.assertEqual(old, new)
|
|
|
|
def test_read(self):
|
|
with open(os_helper.TESTFN, "w+b") as fobj:
|
|
fobj.write(b"spam")
|
|
fobj.flush()
|
|
fd = fobj.fileno()
|
|
os.lseek(fd, 0, 0)
|
|
s = os.read(fd, 4)
|
|
self.assertEqual(type(s), bytes)
|
|
self.assertEqual(s, b"spam")
|
|
|
|
@support.cpython_only
|
|
# Skip the test on 32-bit platforms: the number of bytes must fit in a
|
|
# Py_ssize_t type
|
|
@unittest.skipUnless(INT_MAX < PY_SSIZE_T_MAX,
|
|
"needs INT_MAX < PY_SSIZE_T_MAX")
|
|
@support.bigmemtest(size=INT_MAX + 10, memuse=1, dry_run=False)
|
|
def test_large_read(self, size):
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
create_file(os_helper.TESTFN, b'test')
|
|
|
|
# Issue #21932: Make sure that os.read() does not raise an
|
|
# OverflowError for size larger than INT_MAX
|
|
with open(os_helper.TESTFN, "rb") as fp:
|
|
data = os.read(fp.fileno(), size)
|
|
|
|
# The test does not try to read more than 2 GiB at once because the
|
|
# operating system is free to return less bytes than requested.
|
|
self.assertEqual(data, b'test')
|
|
|
|
def test_write(self):
|
|
# os.write() accepts bytes- and buffer-like objects but not strings
|
|
fd = os.open(os_helper.TESTFN, os.O_CREAT | os.O_WRONLY)
|
|
self.assertRaises(TypeError, os.write, fd, "beans")
|
|
os.write(fd, b"bacon\n")
|
|
os.write(fd, bytearray(b"eggs\n"))
|
|
os.write(fd, memoryview(b"spam\n"))
|
|
os.close(fd)
|
|
with open(os_helper.TESTFN, "rb") as fobj:
|
|
self.assertEqual(fobj.read().splitlines(),
|
|
[b"bacon", b"eggs", b"spam"])
|
|
|
|
def write_windows_console(self, *args):
|
|
retcode = subprocess.call(args,
|
|
# use a new console to not flood the test output
|
|
creationflags=subprocess.CREATE_NEW_CONSOLE,
|
|
# use a shell to hide the console window (SW_HIDE)
|
|
shell=True)
|
|
self.assertEqual(retcode, 0)
|
|
|
|
@unittest.skipUnless(sys.platform == 'win32',
|
|
'test specific to the Windows console')
|
|
def test_write_windows_console(self):
|
|
# Issue #11395: the Windows console returns an error (12: not enough
|
|
# space error) on writing into stdout if stdout mode is binary and the
|
|
# length is greater than 66,000 bytes (or less, depending on heap
|
|
# usage).
|
|
code = "print('x' * 100000)"
|
|
self.write_windows_console(sys.executable, "-c", code)
|
|
self.write_windows_console(sys.executable, "-u", "-c", code)
|
|
|
|
def fdopen_helper(self, *args):
|
|
fd = os.open(os_helper.TESTFN, os.O_RDONLY)
|
|
f = os.fdopen(fd, *args, encoding="utf-8")
|
|
f.close()
|
|
|
|
def test_fdopen(self):
|
|
fd = os.open(os_helper.TESTFN, os.O_CREAT|os.O_RDWR)
|
|
os.close(fd)
|
|
|
|
self.fdopen_helper()
|
|
self.fdopen_helper('r')
|
|
self.fdopen_helper('r', 100)
|
|
|
|
def test_replace(self):
|
|
TESTFN2 = os_helper.TESTFN + ".2"
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
self.addCleanup(os_helper.unlink, TESTFN2)
|
|
|
|
create_file(os_helper.TESTFN, b"1")
|
|
create_file(TESTFN2, b"2")
|
|
|
|
os.replace(os_helper.TESTFN, TESTFN2)
|
|
self.assertRaises(FileNotFoundError, os.stat, os_helper.TESTFN)
|
|
with open(TESTFN2, 'r', encoding='utf-8') as f:
|
|
self.assertEqual(f.read(), "1")
|
|
|
|
def test_open_keywords(self):
|
|
f = os.open(path=__file__, flags=os.O_RDONLY, mode=0o777,
|
|
dir_fd=None)
|
|
os.close(f)
|
|
|
|
def test_symlink_keywords(self):
|
|
symlink = support.get_attribute(os, "symlink")
|
|
try:
|
|
symlink(src='target', dst=os_helper.TESTFN,
|
|
target_is_directory=False, dir_fd=None)
|
|
except (NotImplementedError, OSError):
|
|
pass # No OS support or unprivileged user
|
|
|
|
@unittest.skipUnless(hasattr(os, 'copy_file_range'), 'test needs os.copy_file_range()')
|
|
def test_copy_file_range_invalid_values(self):
|
|
with self.assertRaises(ValueError):
|
|
os.copy_file_range(0, 1, -10)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'copy_file_range'), 'test needs os.copy_file_range()')
|
|
def test_copy_file_range(self):
|
|
TESTFN2 = os_helper.TESTFN + ".3"
|
|
data = b'0123456789'
|
|
|
|
create_file(os_helper.TESTFN, data)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
in_file = open(os_helper.TESTFN, 'rb')
|
|
self.addCleanup(in_file.close)
|
|
in_fd = in_file.fileno()
|
|
|
|
out_file = open(TESTFN2, 'w+b')
|
|
self.addCleanup(os_helper.unlink, TESTFN2)
|
|
self.addCleanup(out_file.close)
|
|
out_fd = out_file.fileno()
|
|
|
|
try:
|
|
i = os.copy_file_range(in_fd, out_fd, 5)
|
|
except OSError as e:
|
|
# Handle the case in which Python was compiled
|
|
# in a system with the syscall but without support
|
|
# in the kernel.
|
|
if e.errno != errno.ENOSYS:
|
|
raise
|
|
self.skipTest(e)
|
|
else:
|
|
# The number of copied bytes can be less than
|
|
# the number of bytes originally requested.
|
|
self.assertIn(i, range(0, 6));
|
|
|
|
with open(TESTFN2, 'rb') as in_file:
|
|
self.assertEqual(in_file.read(), data[:i])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'copy_file_range'), 'test needs os.copy_file_range()')
|
|
def test_copy_file_range_offset(self):
|
|
TESTFN4 = os_helper.TESTFN + ".4"
|
|
data = b'0123456789'
|
|
bytes_to_copy = 6
|
|
in_skip = 3
|
|
out_seek = 5
|
|
|
|
create_file(os_helper.TESTFN, data)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
in_file = open(os_helper.TESTFN, 'rb')
|
|
self.addCleanup(in_file.close)
|
|
in_fd = in_file.fileno()
|
|
|
|
out_file = open(TESTFN4, 'w+b')
|
|
self.addCleanup(os_helper.unlink, TESTFN4)
|
|
self.addCleanup(out_file.close)
|
|
out_fd = out_file.fileno()
|
|
|
|
try:
|
|
i = os.copy_file_range(in_fd, out_fd, bytes_to_copy,
|
|
offset_src=in_skip,
|
|
offset_dst=out_seek)
|
|
except OSError as e:
|
|
# Handle the case in which Python was compiled
|
|
# in a system with the syscall but without support
|
|
# in the kernel.
|
|
if e.errno != errno.ENOSYS:
|
|
raise
|
|
self.skipTest(e)
|
|
else:
|
|
# The number of copied bytes can be less than
|
|
# the number of bytes originally requested.
|
|
self.assertIn(i, range(0, bytes_to_copy+1));
|
|
|
|
with open(TESTFN4, 'rb') as in_file:
|
|
read = in_file.read()
|
|
# seeked bytes (5) are zero'ed
|
|
self.assertEqual(read[:out_seek], b'\x00'*out_seek)
|
|
# 012 are skipped (in_skip)
|
|
# 345678 are copied in the file (in_skip + bytes_to_copy)
|
|
self.assertEqual(read[out_seek:],
|
|
data[in_skip:in_skip+i])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
|
|
def test_splice_invalid_values(self):
|
|
with self.assertRaises(ValueError):
|
|
os.splice(0, 1, -10)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
|
|
@requires_splice_pipe
|
|
def test_splice(self):
|
|
TESTFN2 = os_helper.TESTFN + ".3"
|
|
data = b'0123456789'
|
|
|
|
create_file(os_helper.TESTFN, data)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
in_file = open(os_helper.TESTFN, 'rb')
|
|
self.addCleanup(in_file.close)
|
|
in_fd = in_file.fileno()
|
|
|
|
read_fd, write_fd = os.pipe()
|
|
self.addCleanup(lambda: os.close(read_fd))
|
|
self.addCleanup(lambda: os.close(write_fd))
|
|
|
|
try:
|
|
i = os.splice(in_fd, write_fd, 5)
|
|
except OSError as e:
|
|
# Handle the case in which Python was compiled
|
|
# in a system with the syscall but without support
|
|
# in the kernel.
|
|
if e.errno != errno.ENOSYS:
|
|
raise
|
|
self.skipTest(e)
|
|
else:
|
|
# The number of copied bytes can be less than
|
|
# the number of bytes originally requested.
|
|
self.assertIn(i, range(0, 6));
|
|
|
|
self.assertEqual(os.read(read_fd, 100), data[:i])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
|
|
@requires_splice_pipe
|
|
def test_splice_offset_in(self):
|
|
TESTFN4 = os_helper.TESTFN + ".4"
|
|
data = b'0123456789'
|
|
bytes_to_copy = 6
|
|
in_skip = 3
|
|
|
|
create_file(os_helper.TESTFN, data)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
in_file = open(os_helper.TESTFN, 'rb')
|
|
self.addCleanup(in_file.close)
|
|
in_fd = in_file.fileno()
|
|
|
|
read_fd, write_fd = os.pipe()
|
|
self.addCleanup(lambda: os.close(read_fd))
|
|
self.addCleanup(lambda: os.close(write_fd))
|
|
|
|
try:
|
|
i = os.splice(in_fd, write_fd, bytes_to_copy, offset_src=in_skip)
|
|
except OSError as e:
|
|
# Handle the case in which Python was compiled
|
|
# in a system with the syscall but without support
|
|
# in the kernel.
|
|
if e.errno != errno.ENOSYS:
|
|
raise
|
|
self.skipTest(e)
|
|
else:
|
|
# The number of copied bytes can be less than
|
|
# the number of bytes originally requested.
|
|
self.assertIn(i, range(0, bytes_to_copy+1));
|
|
|
|
read = os.read(read_fd, 100)
|
|
# 012 are skipped (in_skip)
|
|
# 345678 are copied in the file (in_skip + bytes_to_copy)
|
|
self.assertEqual(read, data[in_skip:in_skip+i])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
|
|
@requires_splice_pipe
|
|
def test_splice_offset_out(self):
|
|
TESTFN4 = os_helper.TESTFN + ".4"
|
|
data = b'0123456789'
|
|
bytes_to_copy = 6
|
|
out_seek = 3
|
|
|
|
create_file(os_helper.TESTFN, data)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
read_fd, write_fd = os.pipe()
|
|
self.addCleanup(lambda: os.close(read_fd))
|
|
self.addCleanup(lambda: os.close(write_fd))
|
|
os.write(write_fd, data)
|
|
|
|
out_file = open(TESTFN4, 'w+b')
|
|
self.addCleanup(os_helper.unlink, TESTFN4)
|
|
self.addCleanup(out_file.close)
|
|
out_fd = out_file.fileno()
|
|
|
|
try:
|
|
i = os.splice(read_fd, out_fd, bytes_to_copy, offset_dst=out_seek)
|
|
except OSError as e:
|
|
# Handle the case in which Python was compiled
|
|
# in a system with the syscall but without support
|
|
# in the kernel.
|
|
if e.errno != errno.ENOSYS:
|
|
raise
|
|
self.skipTest(e)
|
|
else:
|
|
# The number of copied bytes can be less than
|
|
# the number of bytes originally requested.
|
|
self.assertIn(i, range(0, bytes_to_copy+1));
|
|
|
|
with open(TESTFN4, 'rb') as in_file:
|
|
read = in_file.read()
|
|
# seeked bytes (5) are zero'ed
|
|
self.assertEqual(read[:out_seek], b'\x00'*out_seek)
|
|
# 012 are skipped (in_skip)
|
|
# 345678 are copied in the file (in_skip + bytes_to_copy)
|
|
self.assertEqual(read[out_seek:], data[:i])
|
|
|
|
|
|
# Test attributes on return values from os.*stat* family.
|
|
class StatAttributeTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.fname = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, self.fname)
|
|
create_file(self.fname, b"ABC")
|
|
|
|
def check_stat_attributes(self, fname):
|
|
result = os.stat(fname)
|
|
|
|
# Make sure direct access works
|
|
self.assertEqual(result[stat.ST_SIZE], 3)
|
|
self.assertEqual(result.st_size, 3)
|
|
|
|
# Make sure all the attributes are there
|
|
members = dir(result)
|
|
for name in dir(stat):
|
|
if name[:3] == 'ST_':
|
|
attr = name.lower()
|
|
if name.endswith("TIME"):
|
|
def trunc(x): return int(x)
|
|
else:
|
|
def trunc(x): return x
|
|
self.assertEqual(trunc(getattr(result, attr)),
|
|
result[getattr(stat, name)])
|
|
self.assertIn(attr, members)
|
|
|
|
# Make sure that the st_?time and st_?time_ns fields roughly agree
|
|
# (they should always agree up to around tens-of-microseconds)
|
|
for name in 'st_atime st_mtime st_ctime'.split():
|
|
floaty = int(getattr(result, name) * 100000)
|
|
nanosecondy = getattr(result, name + "_ns") // 10000
|
|
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
|
|
|
|
# Ensure both birthtime and birthtime_ns roughly agree, if present
|
|
try:
|
|
floaty = int(result.st_birthtime * 100000)
|
|
nanosecondy = result.st_birthtime_ns // 10000
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
|
|
|
|
try:
|
|
result[200]
|
|
self.fail("No exception raised")
|
|
except IndexError:
|
|
pass
|
|
|
|
# Make sure that assignment fails
|
|
try:
|
|
result.st_mode = 1
|
|
self.fail("No exception raised")
|
|
except AttributeError:
|
|
pass
|
|
|
|
try:
|
|
result.st_rdev = 1
|
|
self.fail("No exception raised")
|
|
except (AttributeError, TypeError):
|
|
pass
|
|
|
|
try:
|
|
result.parrot = 1
|
|
self.fail("No exception raised")
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Use the stat_result constructor with a too-short tuple.
|
|
try:
|
|
result2 = os.stat_result((10,))
|
|
self.fail("No exception raised")
|
|
except TypeError:
|
|
pass
|
|
|
|
# Use the constructor with a too-long tuple.
|
|
try:
|
|
result2 = os.stat_result((0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))
|
|
except TypeError:
|
|
pass
|
|
|
|
def test_stat_attributes(self):
|
|
self.check_stat_attributes(self.fname)
|
|
|
|
def test_stat_attributes_bytes(self):
|
|
try:
|
|
fname = self.fname.encode(sys.getfilesystemencoding())
|
|
except UnicodeEncodeError:
|
|
self.skipTest("cannot encode %a for the filesystem" % self.fname)
|
|
self.check_stat_attributes(fname)
|
|
|
|
def test_stat_result_pickle(self):
|
|
result = os.stat(self.fname)
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
with self.subTest(f'protocol {proto}'):
|
|
p = pickle.dumps(result, proto)
|
|
self.assertIn(b'stat_result', p)
|
|
if proto < 4:
|
|
self.assertIn(b'cos\nstat_result\n', p)
|
|
unpickled = pickle.loads(p)
|
|
self.assertEqual(result, unpickled)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()')
|
|
def test_statvfs_attributes(self):
|
|
result = os.statvfs(self.fname)
|
|
|
|
# Make sure direct access works
|
|
self.assertEqual(result.f_bfree, result[3])
|
|
|
|
# Make sure all the attributes are there.
|
|
members = ('bsize', 'frsize', 'blocks', 'bfree', 'bavail', 'files',
|
|
'ffree', 'favail', 'flag', 'namemax')
|
|
for value, member in enumerate(members):
|
|
self.assertEqual(getattr(result, 'f_' + member), result[value])
|
|
|
|
self.assertTrue(isinstance(result.f_fsid, int))
|
|
|
|
# Test that the size of the tuple doesn't change
|
|
self.assertEqual(len(result), 10)
|
|
|
|
# Make sure that assignment really fails
|
|
try:
|
|
result.f_bfree = 1
|
|
self.fail("No exception raised")
|
|
except AttributeError:
|
|
pass
|
|
|
|
try:
|
|
result.parrot = 1
|
|
self.fail("No exception raised")
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Use the constructor with a too-short tuple.
|
|
try:
|
|
result2 = os.statvfs_result((10,))
|
|
self.fail("No exception raised")
|
|
except TypeError:
|
|
pass
|
|
|
|
# Use the constructor with a too-long tuple.
|
|
try:
|
|
result2 = os.statvfs_result((0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))
|
|
except TypeError:
|
|
pass
|
|
|
|
@unittest.skipUnless(hasattr(os, 'statvfs'),
|
|
"need os.statvfs()")
|
|
def test_statvfs_result_pickle(self):
|
|
result = os.statvfs(self.fname)
|
|
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
p = pickle.dumps(result, proto)
|
|
self.assertIn(b'statvfs_result', p)
|
|
if proto < 4:
|
|
self.assertIn(b'cos\nstatvfs_result\n', p)
|
|
unpickled = pickle.loads(p)
|
|
self.assertEqual(result, unpickled)
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
def test_1686475(self):
|
|
# Verify that an open file can be stat'ed
|
|
try:
|
|
os.stat(r"c:\pagefile.sys")
|
|
except FileNotFoundError:
|
|
self.skipTest(r'c:\pagefile.sys does not exist')
|
|
except OSError as e:
|
|
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)
|
|
|
|
def check_file_attributes(self, result):
|
|
self.assertTrue(hasattr(result, 'st_file_attributes'))
|
|
self.assertTrue(isinstance(result.st_file_attributes, int))
|
|
self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
|
|
|
|
@unittest.skipUnless(sys.platform == "win32",
|
|
"st_file_attributes is Win32 specific")
|
|
def test_file_attributes(self):
|
|
# test file st_file_attributes (FILE_ATTRIBUTE_DIRECTORY not set)
|
|
result = os.stat(self.fname)
|
|
self.check_file_attributes(result)
|
|
self.assertEqual(
|
|
result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY,
|
|
0)
|
|
|
|
# test directory st_file_attributes (FILE_ATTRIBUTE_DIRECTORY set)
|
|
dirname = os_helper.TESTFN + "dir"
|
|
os.mkdir(dirname)
|
|
self.addCleanup(os.rmdir, dirname)
|
|
|
|
result = os.stat(dirname)
|
|
self.check_file_attributes(result)
|
|
self.assertEqual(
|
|
result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY,
|
|
stat.FILE_ATTRIBUTE_DIRECTORY)
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
def test_access_denied(self):
|
|
# Default to FindFirstFile WIN32_FIND_DATA when access is
|
|
# denied. See issue 28075.
|
|
# os.environ['TEMP'] should be located on a volume that
|
|
# supports file ACLs.
|
|
fname = os.path.join(os.environ['TEMP'], self.fname + "_access")
|
|
self.addCleanup(os_helper.unlink, fname)
|
|
create_file(fname, b'ABC')
|
|
# Deny the right to [S]YNCHRONIZE on the file to
|
|
# force CreateFile to fail with ERROR_ACCESS_DENIED.
|
|
DETACHED_PROCESS = 8
|
|
subprocess.check_call(
|
|
# bpo-30584: Use security identifier *S-1-5-32-545 instead
|
|
# of localized "Users" to not depend on the locale.
|
|
['icacls.exe', fname, '/deny', '*S-1-5-32-545:(S)'],
|
|
creationflags=DETACHED_PROCESS
|
|
)
|
|
result = os.stat(fname)
|
|
self.assertNotEqual(result.st_size, 0)
|
|
self.assertTrue(os.path.isfile(fname))
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
def test_stat_block_device(self):
|
|
# bpo-38030: os.stat fails for block devices
|
|
# Test a filename like "//./C:"
|
|
fname = "//./" + os.path.splitdrive(os.getcwd())[0]
|
|
result = os.stat(fname)
|
|
self.assertEqual(result.st_mode, stat.S_IFBLK)
|
|
|
|
|
|
class UtimeTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.dirname = os_helper.TESTFN
|
|
self.fname = os.path.join(self.dirname, "f1")
|
|
|
|
self.addCleanup(os_helper.rmtree, self.dirname)
|
|
os.mkdir(self.dirname)
|
|
create_file(self.fname)
|
|
|
|
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', 0) 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)
|
|
with os_helper.open_dir_fd(dirname) as dirfd:
|
|
# pass dir_fd to test utimensat(timespec) or futimesat(timeval)
|
|
os.utime(name, dir_fd=dirfd, ns=ns)
|
|
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.
|
|
# bpo-30649: Tolerate 50 ms for slow Windows buildbots.
|
|
#
|
|
# x86 Gentoo Refleaks 3.x once failed with dt=20.2 ms. So use
|
|
# also 50 ms on other platforms.
|
|
delta = 0.050
|
|
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 test_utime_nonexistent(self):
|
|
now = time.time()
|
|
filename = 'nonexistent'
|
|
with self.assertRaises(FileNotFoundError) as cm:
|
|
os.utime(filename, (now, now))
|
|
self.assertEqual(cm.exception.filename, filename)
|
|
|
|
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))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, [5, 5])
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, (5,))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, (5, 5, 5))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=[5, 5])
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=(5,))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=(5, 5, 5))
|
|
|
|
if os.utime not in os.supports_follow_symlinks:
|
|
with self.assertRaises(NotImplementedError):
|
|
os.utime(self.fname, (5, 5), follow_symlinks=False)
|
|
if os.utime not in os.supports_fd:
|
|
with open(self.fname, 'wb', 0) as fp:
|
|
with self.assertRaises(TypeError):
|
|
os.utime(fp.fileno(), (5, 5))
|
|
if os.utime not in os.supports_dir_fd:
|
|
with self.assertRaises(NotImplementedError):
|
|
os.utime(self.fname, (5, 5), dir_fd=0)
|
|
|
|
@support.cpython_only
|
|
def test_issue31577(self):
|
|
# The interpreter shouldn't crash in case utime() received a bad
|
|
# ns argument.
|
|
def get_bad_int(divmod_ret_val):
|
|
class BadInt:
|
|
def __divmod__(*args):
|
|
return divmod_ret_val
|
|
return BadInt()
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=(get_bad_int(42), 1))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=(get_bad_int(()), 1))
|
|
with self.assertRaises(TypeError):
|
|
os.utime(self.fname, ns=(get_bad_int((1, 2, 3)), 1))
|
|
|
|
|
|
from test import mapping_tests
|
|
|
|
class EnvironTests(mapping_tests.BasicTestMappingProtocol):
|
|
"""check that os.environ object conform to mapping protocol"""
|
|
type2test = None
|
|
|
|
def setUp(self):
|
|
self.__save = dict(os.environ)
|
|
if os.supports_bytes_environ:
|
|
self.__saveb = dict(os.environb)
|
|
for key, value in self._reference().items():
|
|
os.environ[key] = value
|
|
|
|
def tearDown(self):
|
|
os.environ.clear()
|
|
os.environ.update(self.__save)
|
|
if os.supports_bytes_environ:
|
|
os.environb.clear()
|
|
os.environb.update(self.__saveb)
|
|
|
|
def _reference(self):
|
|
return {"KEY1":"VALUE1", "KEY2":"VALUE2", "KEY3":"VALUE3"}
|
|
|
|
def _empty_mapping(self):
|
|
os.environ.clear()
|
|
return os.environ
|
|
|
|
# Bug 1110478
|
|
@unittest.skipUnless(unix_shell and os.path.exists(unix_shell),
|
|
'requires a shell')
|
|
@unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()")
|
|
@support.requires_subprocess()
|
|
def test_update2(self):
|
|
os.environ.clear()
|
|
os.environ.update(HELLO="World")
|
|
with os.popen("%s -c 'echo $HELLO'" % unix_shell) as popen:
|
|
value = popen.read().strip()
|
|
self.assertEqual(value, "World")
|
|
|
|
@unittest.skipUnless(unix_shell and os.path.exists(unix_shell),
|
|
'requires a shell')
|
|
@unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()")
|
|
@support.requires_subprocess()
|
|
def test_os_popen_iter(self):
|
|
with os.popen("%s -c 'echo \"line1\nline2\nline3\"'"
|
|
% unix_shell) as popen:
|
|
it = iter(popen)
|
|
self.assertEqual(next(it), "line1\n")
|
|
self.assertEqual(next(it), "line2\n")
|
|
self.assertEqual(next(it), "line3\n")
|
|
self.assertRaises(StopIteration, next, it)
|
|
|
|
# Verify environ keys and values from the OS are of the
|
|
# correct str type.
|
|
def test_keyvalue_types(self):
|
|
for key, val in os.environ.items():
|
|
self.assertEqual(type(key), str)
|
|
self.assertEqual(type(val), str)
|
|
|
|
def test_items(self):
|
|
for key, value in self._reference().items():
|
|
self.assertEqual(os.environ.get(key), value)
|
|
|
|
# Issue 7310
|
|
def test___repr__(self):
|
|
"""Check that the repr() of os.environ looks like environ({...})."""
|
|
env = os.environ
|
|
formatted_items = ", ".join(
|
|
f"{key!r}: {value!r}"
|
|
for key, value in env.items()
|
|
)
|
|
self.assertEqual(repr(env), f"environ({{{formatted_items}}})")
|
|
|
|
def test_get_exec_path(self):
|
|
defpath_list = os.defpath.split(os.pathsep)
|
|
test_path = ['/monty', '/python', '', '/flying/circus']
|
|
test_env = {'PATH': os.pathsep.join(test_path)}
|
|
|
|
saved_environ = os.environ
|
|
try:
|
|
os.environ = dict(test_env)
|
|
# Test that defaulting to os.environ works.
|
|
self.assertSequenceEqual(test_path, os.get_exec_path())
|
|
self.assertSequenceEqual(test_path, os.get_exec_path(env=None))
|
|
finally:
|
|
os.environ = saved_environ
|
|
|
|
# No PATH environment variable
|
|
self.assertSequenceEqual(defpath_list, os.get_exec_path({}))
|
|
# Empty PATH environment variable
|
|
self.assertSequenceEqual(('',), os.get_exec_path({'PATH':''}))
|
|
# Supplied PATH environment variable
|
|
self.assertSequenceEqual(test_path, os.get_exec_path(test_env))
|
|
|
|
if os.supports_bytes_environ:
|
|
# env cannot contain 'PATH' and b'PATH' keys
|
|
try:
|
|
# ignore BytesWarning warning
|
|
with warnings.catch_warnings(record=True):
|
|
mixed_env = {'PATH': '1', b'PATH': b'2'}
|
|
except BytesWarning:
|
|
# mixed_env cannot be created with python -bb
|
|
pass
|
|
else:
|
|
self.assertRaises(ValueError, os.get_exec_path, mixed_env)
|
|
|
|
# bytes key and/or value
|
|
self.assertSequenceEqual(os.get_exec_path({b'PATH': b'abc'}),
|
|
['abc'])
|
|
self.assertSequenceEqual(os.get_exec_path({b'PATH': 'abc'}),
|
|
['abc'])
|
|
self.assertSequenceEqual(os.get_exec_path({'PATH': b'abc'}),
|
|
['abc'])
|
|
|
|
@unittest.skipUnless(os.supports_bytes_environ,
|
|
"os.environb required for this test.")
|
|
def test_environb(self):
|
|
# os.environ -> os.environb
|
|
value = 'euro\u20ac'
|
|
try:
|
|
value_bytes = value.encode(sys.getfilesystemencoding(),
|
|
'surrogateescape')
|
|
except UnicodeEncodeError:
|
|
msg = "U+20AC character is not encodable to %s" % (
|
|
sys.getfilesystemencoding(),)
|
|
self.skipTest(msg)
|
|
os.environ['unicode'] = value
|
|
self.assertEqual(os.environ['unicode'], value)
|
|
self.assertEqual(os.environb[b'unicode'], value_bytes)
|
|
|
|
# os.environb -> os.environ
|
|
value = b'\xff'
|
|
os.environb[b'bytes'] = value
|
|
self.assertEqual(os.environb[b'bytes'], value)
|
|
value_str = value.decode(sys.getfilesystemencoding(), 'surrogateescape')
|
|
self.assertEqual(os.environ['bytes'], value_str)
|
|
|
|
@support.requires_subprocess()
|
|
def test_putenv_unsetenv(self):
|
|
name = "PYTHONTESTVAR"
|
|
value = "testvalue"
|
|
code = f'import os; print(repr(os.environ.get({name!r})))'
|
|
|
|
with os_helper.EnvironmentVarGuard() as env:
|
|
env.pop(name, None)
|
|
|
|
os.putenv(name, value)
|
|
proc = subprocess.run([sys.executable, '-c', code], check=True,
|
|
stdout=subprocess.PIPE, text=True)
|
|
self.assertEqual(proc.stdout.rstrip(), repr(value))
|
|
|
|
os.unsetenv(name)
|
|
proc = subprocess.run([sys.executable, '-c', code], check=True,
|
|
stdout=subprocess.PIPE, text=True)
|
|
self.assertEqual(proc.stdout.rstrip(), repr(None))
|
|
|
|
# On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415).
|
|
@support.requires_mac_ver(10, 6)
|
|
def test_putenv_unsetenv_error(self):
|
|
# Empty variable name is invalid.
|
|
# "=" and null character are not allowed in a variable name.
|
|
for name in ('', '=name', 'na=me', 'name='):
|
|
self.assertRaises((OSError, ValueError), os.putenv, name, "value")
|
|
self.assertRaises((OSError, ValueError), os.unsetenv, name)
|
|
for name in ('name\0', 'na\0me'):
|
|
self.assertRaises(ValueError, os.putenv, name, "value")
|
|
self.assertRaises(ValueError, os.unsetenv, name)
|
|
|
|
if sys.platform == "win32":
|
|
# On Windows, an environment variable string ("name=value" string)
|
|
# is limited to 32,767 characters
|
|
longstr = 'x' * 32_768
|
|
self.assertRaises(ValueError, os.putenv, longstr, "1")
|
|
self.assertRaises(ValueError, os.putenv, "X", longstr)
|
|
self.assertRaises(ValueError, os.unsetenv, longstr)
|
|
|
|
def test_key_type(self):
|
|
missing = 'missingkey'
|
|
self.assertNotIn(missing, os.environ)
|
|
|
|
with self.assertRaises(KeyError) as cm:
|
|
os.environ[missing]
|
|
self.assertIs(cm.exception.args[0], missing)
|
|
self.assertTrue(cm.exception.__suppress_context__)
|
|
|
|
with self.assertRaises(KeyError) as cm:
|
|
del os.environ[missing]
|
|
self.assertIs(cm.exception.args[0], missing)
|
|
self.assertTrue(cm.exception.__suppress_context__)
|
|
|
|
def _test_environ_iteration(self, collection):
|
|
iterator = iter(collection)
|
|
new_key = "__new_key__"
|
|
|
|
next(iterator) # start iteration over os.environ.items
|
|
|
|
# add a new key in os.environ mapping
|
|
os.environ[new_key] = "test_environ_iteration"
|
|
|
|
try:
|
|
next(iterator) # force iteration over modified mapping
|
|
self.assertEqual(os.environ[new_key], "test_environ_iteration")
|
|
finally:
|
|
del os.environ[new_key]
|
|
|
|
def test_iter_error_when_changing_os_environ(self):
|
|
self._test_environ_iteration(os.environ)
|
|
|
|
def test_iter_error_when_changing_os_environ_items(self):
|
|
self._test_environ_iteration(os.environ.items())
|
|
|
|
def test_iter_error_when_changing_os_environ_values(self):
|
|
self._test_environ_iteration(os.environ.values())
|
|
|
|
def _test_underlying_process_env(self, var, expected):
|
|
if not (unix_shell and os.path.exists(unix_shell)):
|
|
return
|
|
elif not support.has_subprocess_support:
|
|
return
|
|
|
|
with os.popen(f"{unix_shell} -c 'echo ${var}'") as popen:
|
|
value = popen.read().strip()
|
|
|
|
self.assertEqual(expected, value)
|
|
|
|
def test_or_operator(self):
|
|
overridden_key = '_TEST_VAR_'
|
|
original_value = 'original_value'
|
|
os.environ[overridden_key] = original_value
|
|
|
|
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
|
|
expected = dict(os.environ)
|
|
expected.update(new_vars_dict)
|
|
|
|
actual = os.environ | new_vars_dict
|
|
self.assertDictEqual(expected, actual)
|
|
self.assertEqual('3', actual[overridden_key])
|
|
|
|
new_vars_items = new_vars_dict.items()
|
|
self.assertIs(NotImplemented, os.environ.__or__(new_vars_items))
|
|
|
|
self._test_underlying_process_env('_A_', '')
|
|
self._test_underlying_process_env(overridden_key, original_value)
|
|
|
|
def test_ior_operator(self):
|
|
overridden_key = '_TEST_VAR_'
|
|
os.environ[overridden_key] = 'original_value'
|
|
|
|
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
|
|
expected = dict(os.environ)
|
|
expected.update(new_vars_dict)
|
|
|
|
os.environ |= new_vars_dict
|
|
self.assertEqual(expected, os.environ)
|
|
self.assertEqual('3', os.environ[overridden_key])
|
|
|
|
self._test_underlying_process_env('_A_', '1')
|
|
self._test_underlying_process_env(overridden_key, '3')
|
|
|
|
def test_ior_operator_invalid_dicts(self):
|
|
os_environ_copy = os.environ.copy()
|
|
with self.assertRaises(TypeError):
|
|
dict_with_bad_key = {1: '_A_'}
|
|
os.environ |= dict_with_bad_key
|
|
|
|
with self.assertRaises(TypeError):
|
|
dict_with_bad_val = {'_A_': 1}
|
|
os.environ |= dict_with_bad_val
|
|
|
|
# Check nothing was added.
|
|
self.assertEqual(os_environ_copy, os.environ)
|
|
|
|
def test_ior_operator_key_value_iterable(self):
|
|
overridden_key = '_TEST_VAR_'
|
|
os.environ[overridden_key] = 'original_value'
|
|
|
|
new_vars_items = (('_A_', '1'), ('_B_', '2'), (overridden_key, '3'))
|
|
expected = dict(os.environ)
|
|
expected.update(new_vars_items)
|
|
|
|
os.environ |= new_vars_items
|
|
self.assertEqual(expected, os.environ)
|
|
self.assertEqual('3', os.environ[overridden_key])
|
|
|
|
self._test_underlying_process_env('_A_', '1')
|
|
self._test_underlying_process_env(overridden_key, '3')
|
|
|
|
def test_ror_operator(self):
|
|
overridden_key = '_TEST_VAR_'
|
|
original_value = 'original_value'
|
|
os.environ[overridden_key] = original_value
|
|
|
|
new_vars_dict = {'_A_': '1', '_B_': '2', overridden_key: '3'}
|
|
expected = dict(new_vars_dict)
|
|
expected.update(os.environ)
|
|
|
|
actual = new_vars_dict | os.environ
|
|
self.assertDictEqual(expected, actual)
|
|
self.assertEqual(original_value, actual[overridden_key])
|
|
|
|
new_vars_items = new_vars_dict.items()
|
|
self.assertIs(NotImplemented, os.environ.__ror__(new_vars_items))
|
|
|
|
self._test_underlying_process_env('_A_', '')
|
|
self._test_underlying_process_env(overridden_key, original_value)
|
|
|
|
|
|
class WalkTests(unittest.TestCase):
|
|
"""Tests for os.walk()."""
|
|
is_fwalk = False
|
|
|
|
# Wrapper to hide minor differences between os.walk and os.fwalk
|
|
# to tests both functions with the same code base
|
|
def walk(self, top, **kwargs):
|
|
if 'follow_symlinks' in kwargs:
|
|
kwargs['followlinks'] = kwargs.pop('follow_symlinks')
|
|
return os.walk(top, **kwargs)
|
|
|
|
def setUp(self):
|
|
join = os.path.join
|
|
self.addCleanup(os_helper.rmtree, os_helper.TESTFN)
|
|
|
|
# Build:
|
|
# TESTFN/
|
|
# TEST1/ a file kid and two directory kids
|
|
# tmp1
|
|
# SUB1/ a file kid and a directory kid
|
|
# tmp2
|
|
# SUB11/ no kids
|
|
# SUB2/ a file kid and a dirsymlink kid
|
|
# tmp3
|
|
# SUB21/ not readable
|
|
# tmp5
|
|
# link/ a symlink to TESTFN.2
|
|
# broken_link
|
|
# broken_link2
|
|
# broken_link3
|
|
# TEST2/
|
|
# tmp4 a lone file
|
|
self.walk_path = join(os_helper.TESTFN, "TEST1")
|
|
self.sub1_path = join(self.walk_path, "SUB1")
|
|
self.sub11_path = join(self.sub1_path, "SUB11")
|
|
sub2_path = join(self.walk_path, "SUB2")
|
|
sub21_path = join(sub2_path, "SUB21")
|
|
self.tmp1_path = join(self.walk_path, "tmp1")
|
|
tmp2_path = join(self.sub1_path, "tmp2")
|
|
tmp3_path = join(sub2_path, "tmp3")
|
|
tmp5_path = join(sub21_path, "tmp3")
|
|
self.link_path = join(sub2_path, "link")
|
|
t2_path = join(os_helper.TESTFN, "TEST2")
|
|
tmp4_path = join(os_helper.TESTFN, "TEST2", "tmp4")
|
|
self.broken_link_path = join(sub2_path, "broken_link")
|
|
broken_link2_path = join(sub2_path, "broken_link2")
|
|
broken_link3_path = join(sub2_path, "broken_link3")
|
|
|
|
# Create stuff.
|
|
os.makedirs(self.sub11_path)
|
|
os.makedirs(sub2_path)
|
|
os.makedirs(sub21_path)
|
|
os.makedirs(t2_path)
|
|
|
|
for path in self.tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path:
|
|
with open(path, "x", encoding='utf-8') as f:
|
|
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
|
|
|
|
if os_helper.can_symlink():
|
|
os.symlink(os.path.abspath(t2_path), self.link_path)
|
|
os.symlink('broken', self.broken_link_path, True)
|
|
os.symlink(join('tmp3', 'broken'), broken_link2_path, True)
|
|
os.symlink(join('SUB21', 'tmp5'), broken_link3_path, True)
|
|
self.sub2_tree = (sub2_path, ["SUB21", "link"],
|
|
["broken_link", "broken_link2", "broken_link3",
|
|
"tmp3"])
|
|
else:
|
|
self.sub2_tree = (sub2_path, ["SUB21"], ["tmp3"])
|
|
|
|
if not support.is_emscripten:
|
|
# Emscripten fails with inaccessible directory
|
|
os.chmod(sub21_path, 0)
|
|
try:
|
|
os.listdir(sub21_path)
|
|
except PermissionError:
|
|
self.addCleanup(os.chmod, sub21_path, stat.S_IRWXU)
|
|
else:
|
|
os.chmod(sub21_path, stat.S_IRWXU)
|
|
os.unlink(tmp5_path)
|
|
os.rmdir(sub21_path)
|
|
del self.sub2_tree[1][:1]
|
|
|
|
def test_walk_topdown(self):
|
|
# Walk top-down.
|
|
all = list(self.walk(self.walk_path))
|
|
|
|
self.assertEqual(len(all), 4)
|
|
# We can't know which order SUB1 and SUB2 will appear in.
|
|
# Not flipped: TESTFN, SUB1, SUB11, SUB2
|
|
# flipped: TESTFN, SUB2, SUB1, SUB11
|
|
flipped = all[0][1][0] != "SUB1"
|
|
all[0][1].sort()
|
|
all[3 - 2 * flipped][-1].sort()
|
|
all[3 - 2 * flipped][1].sort()
|
|
self.assertEqual(all[0], (self.walk_path, ["SUB1", "SUB2"], ["tmp1"]))
|
|
self.assertEqual(all[1 + flipped], (self.sub1_path, ["SUB11"], ["tmp2"]))
|
|
self.assertEqual(all[2 + flipped], (self.sub11_path, [], []))
|
|
self.assertEqual(all[3 - 2 * flipped], self.sub2_tree)
|
|
|
|
def test_walk_prune(self, walk_path=None):
|
|
if walk_path is None:
|
|
walk_path = self.walk_path
|
|
# Prune the search.
|
|
all = []
|
|
for root, dirs, files in self.walk(walk_path):
|
|
all.append((root, dirs, files))
|
|
# Don't descend into SUB1.
|
|
if 'SUB1' in dirs:
|
|
# Note that this also mutates the dirs we appended to all!
|
|
dirs.remove('SUB1')
|
|
|
|
self.assertEqual(len(all), 2)
|
|
self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"]))
|
|
|
|
all[1][-1].sort()
|
|
all[1][1].sort()
|
|
self.assertEqual(all[1], self.sub2_tree)
|
|
|
|
def test_file_like_path(self):
|
|
self.test_walk_prune(FakePath(self.walk_path))
|
|
|
|
def test_walk_bottom_up(self):
|
|
# Walk bottom-up.
|
|
all = list(self.walk(self.walk_path, topdown=False))
|
|
|
|
self.assertEqual(len(all), 4, all)
|
|
# We can't know which order SUB1 and SUB2 will appear in.
|
|
# Not flipped: SUB11, SUB1, SUB2, TESTFN
|
|
# flipped: SUB2, SUB11, SUB1, TESTFN
|
|
flipped = all[3][1][0] != "SUB1"
|
|
all[3][1].sort()
|
|
all[2 - 2 * flipped][-1].sort()
|
|
all[2 - 2 * flipped][1].sort()
|
|
self.assertEqual(all[3],
|
|
(self.walk_path, ["SUB1", "SUB2"], ["tmp1"]))
|
|
self.assertEqual(all[flipped],
|
|
(self.sub11_path, [], []))
|
|
self.assertEqual(all[flipped + 1],
|
|
(self.sub1_path, ["SUB11"], ["tmp2"]))
|
|
self.assertEqual(all[2 - 2 * flipped],
|
|
self.sub2_tree)
|
|
|
|
def test_walk_symlink(self):
|
|
if not os_helper.can_symlink():
|
|
self.skipTest("need symlink support")
|
|
|
|
# Walk, following symlinks.
|
|
walk_it = self.walk(self.walk_path, follow_symlinks=True)
|
|
for root, dirs, files in walk_it:
|
|
if root == self.link_path:
|
|
self.assertEqual(dirs, [])
|
|
self.assertEqual(files, ["tmp4"])
|
|
break
|
|
else:
|
|
self.fail("Didn't follow symlink with followlinks=True")
|
|
|
|
walk_it = self.walk(self.broken_link_path, follow_symlinks=True)
|
|
if self.is_fwalk:
|
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
def test_walk_bad_dir(self):
|
|
# Walk top-down.
|
|
errors = []
|
|
walk_it = self.walk(self.walk_path, onerror=errors.append)
|
|
root, dirs, files = next(walk_it)
|
|
self.assertEqual(errors, [])
|
|
dir1 = 'SUB1'
|
|
path1 = os.path.join(root, dir1)
|
|
path1new = os.path.join(root, dir1 + '.new')
|
|
os.rename(path1, path1new)
|
|
try:
|
|
roots = [r for r, d, f in walk_it]
|
|
self.assertTrue(errors)
|
|
self.assertNotIn(path1, roots)
|
|
self.assertNotIn(path1new, roots)
|
|
for dir2 in dirs:
|
|
if dir2 != dir1:
|
|
self.assertIn(os.path.join(root, dir2), roots)
|
|
finally:
|
|
os.rename(path1new, path1)
|
|
|
|
def test_walk_bad_dir2(self):
|
|
walk_it = self.walk('nonexisting')
|
|
if self.is_fwalk:
|
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
walk_it = self.walk('nonexisting', follow_symlinks=True)
|
|
if self.is_fwalk:
|
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
walk_it = self.walk(self.tmp1_path)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
walk_it = self.walk(self.tmp1_path, follow_symlinks=True)
|
|
if self.is_fwalk:
|
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
|
@unittest.skipIf(sys.platform == "vxworks",
|
|
"fifo requires special path on VxWorks")
|
|
def test_walk_named_pipe(self):
|
|
path = os_helper.TESTFN + '-pipe'
|
|
os.mkfifo(path)
|
|
self.addCleanup(os.unlink, path)
|
|
|
|
walk_it = self.walk(path)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
walk_it = self.walk(path, follow_symlinks=True)
|
|
if self.is_fwalk:
|
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
|
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
|
@unittest.skipIf(sys.platform == "vxworks",
|
|
"fifo requires special path on VxWorks")
|
|
def test_walk_named_pipe2(self):
|
|
path = os_helper.TESTFN + '-dir'
|
|
os.mkdir(path)
|
|
self.addCleanup(shutil.rmtree, path)
|
|
os.mkfifo(os.path.join(path, 'mypipe'))
|
|
|
|
errors = []
|
|
walk_it = self.walk(path, onerror=errors.append)
|
|
next(walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
self.assertEqual(errors, [])
|
|
|
|
errors = []
|
|
walk_it = self.walk(path, onerror=errors.append)
|
|
root, dirs, files = next(walk_it)
|
|
self.assertEqual(root, path)
|
|
self.assertEqual(dirs, [])
|
|
self.assertEqual(files, ['mypipe'])
|
|
dirs.extend(files)
|
|
files.clear()
|
|
if self.is_fwalk:
|
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
|
self.assertRaises(StopIteration, next, walk_it)
|
|
if self.is_fwalk:
|
|
self.assertEqual(errors, [])
|
|
else:
|
|
self.assertEqual(len(errors), 1, errors)
|
|
self.assertIsInstance(errors[0], NotADirectoryError)
|
|
|
|
def test_walk_many_open_files(self):
|
|
depth = 30
|
|
base = os.path.join(os_helper.TESTFN, 'deep')
|
|
p = os.path.join(base, *(['d']*depth))
|
|
os.makedirs(p)
|
|
|
|
iters = [self.walk(base, topdown=False) for j in range(100)]
|
|
for i in range(depth + 1):
|
|
expected = (p, ['d'] if i else [], [])
|
|
for it in iters:
|
|
self.assertEqual(next(it), expected)
|
|
p = os.path.dirname(p)
|
|
|
|
iters = [self.walk(base, topdown=True) for j in range(100)]
|
|
p = base
|
|
for i in range(depth + 1):
|
|
expected = (p, ['d'] if i < depth else [], [])
|
|
for it in iters:
|
|
self.assertEqual(next(it), expected)
|
|
p = os.path.join(p, 'd')
|
|
|
|
def test_walk_above_recursion_limit(self):
|
|
depth = 50
|
|
os.makedirs(os.path.join(self.walk_path, *(['d'] * depth)))
|
|
with infinite_recursion(depth - 5):
|
|
all = list(self.walk(self.walk_path))
|
|
|
|
sub2_path = self.sub2_tree[0]
|
|
for root, dirs, files in all:
|
|
if root == sub2_path:
|
|
dirs.sort()
|
|
files.sort()
|
|
|
|
d_entries = []
|
|
d_path = self.walk_path
|
|
for _ in range(depth):
|
|
d_path = os.path.join(d_path, "d")
|
|
d_entries.append((d_path, ["d"], []))
|
|
d_entries[-1][1].clear()
|
|
|
|
# Sub-sequences where the order is known
|
|
sections = {
|
|
"SUB1": [
|
|
(self.sub1_path, ["SUB11"], ["tmp2"]),
|
|
(self.sub11_path, [], []),
|
|
],
|
|
"SUB2": [self.sub2_tree],
|
|
"d": d_entries,
|
|
}
|
|
|
|
# The ordering of sub-dirs is arbitrary but determines the order in
|
|
# which sub-sequences appear
|
|
dirs = all[0][1]
|
|
expected = [(self.walk_path, dirs, ["tmp1"])]
|
|
for d in dirs:
|
|
expected.extend(sections[d])
|
|
|
|
self.assertEqual(len(all), depth + 4)
|
|
self.assertEqual(sorted(dirs), ["SUB1", "SUB2", "d"])
|
|
self.assertEqual(all, expected)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
|
|
class FwalkTests(WalkTests):
|
|
"""Tests for os.fwalk()."""
|
|
is_fwalk = True
|
|
|
|
def walk(self, top, **kwargs):
|
|
for root, dirs, files, root_fd in self.fwalk(top, **kwargs):
|
|
yield (root, dirs, files)
|
|
|
|
def fwalk(self, *args, **kwargs):
|
|
return os.fwalk(*args, **kwargs)
|
|
|
|
def _compare_to_walk(self, walk_kwargs, fwalk_kwargs):
|
|
"""
|
|
compare with walk() results.
|
|
"""
|
|
walk_kwargs = walk_kwargs.copy()
|
|
fwalk_kwargs = fwalk_kwargs.copy()
|
|
for topdown, follow_symlinks in itertools.product((True, False), repeat=2):
|
|
walk_kwargs.update(topdown=topdown, followlinks=follow_symlinks)
|
|
fwalk_kwargs.update(topdown=topdown, follow_symlinks=follow_symlinks)
|
|
|
|
expected = {}
|
|
for root, dirs, files in os.walk(**walk_kwargs):
|
|
expected[root] = (set(dirs), set(files))
|
|
|
|
for root, dirs, files, rootfd in self.fwalk(**fwalk_kwargs):
|
|
self.assertIn(root, expected)
|
|
self.assertEqual(expected[root], (set(dirs), set(files)))
|
|
|
|
def test_compare_to_walk(self):
|
|
kwargs = {'top': os_helper.TESTFN}
|
|
self._compare_to_walk(kwargs, kwargs)
|
|
|
|
def test_dir_fd(self):
|
|
try:
|
|
fd = os.open(".", os.O_RDONLY)
|
|
walk_kwargs = {'top': os_helper.TESTFN}
|
|
fwalk_kwargs = walk_kwargs.copy()
|
|
fwalk_kwargs['dir_fd'] = fd
|
|
self._compare_to_walk(walk_kwargs, fwalk_kwargs)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
def test_yields_correct_dir_fd(self):
|
|
# check returned file descriptors
|
|
for topdown, follow_symlinks in itertools.product((True, False), repeat=2):
|
|
args = os_helper.TESTFN, topdown, None
|
|
for root, dirs, files, rootfd in self.fwalk(*args, follow_symlinks=follow_symlinks):
|
|
# check that the FD is valid
|
|
os.fstat(rootfd)
|
|
# redundant check
|
|
os.stat(rootfd)
|
|
# check that listdir() returns consistent information
|
|
self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files))
|
|
|
|
@unittest.skipIf(
|
|
support.is_emscripten, "Cannot dup stdout on Emscripten"
|
|
)
|
|
@unittest.skipIf(
|
|
support.is_android, "dup return value is unpredictable on Android"
|
|
)
|
|
def test_fd_leak(self):
|
|
# Since we're opening a lot of FDs, we must be careful to avoid leaks:
|
|
# we both check that calling fwalk() a large number of times doesn't
|
|
# yield EMFILE, and that the minimum allocated FD hasn't changed.
|
|
minfd = os.dup(1)
|
|
os.close(minfd)
|
|
for i in range(256):
|
|
for x in self.fwalk(os_helper.TESTFN):
|
|
pass
|
|
newfd = os.dup(1)
|
|
self.addCleanup(os.close, newfd)
|
|
self.assertEqual(newfd, minfd)
|
|
|
|
# fwalk() keeps file descriptors open
|
|
test_walk_many_open_files = None
|
|
# fwalk() still uses recursion
|
|
test_walk_above_recursion_limit = None
|
|
|
|
|
|
class BytesWalkTests(WalkTests):
|
|
"""Tests for os.walk() with bytes."""
|
|
def walk(self, top, **kwargs):
|
|
if 'follow_symlinks' in kwargs:
|
|
kwargs['followlinks'] = kwargs.pop('follow_symlinks')
|
|
for broot, bdirs, bfiles in os.walk(os.fsencode(top), **kwargs):
|
|
root = os.fsdecode(broot)
|
|
dirs = list(map(os.fsdecode, bdirs))
|
|
files = list(map(os.fsdecode, bfiles))
|
|
yield (root, dirs, files)
|
|
bdirs[:] = list(map(os.fsencode, dirs))
|
|
bfiles[:] = list(map(os.fsencode, files))
|
|
|
|
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
|
|
class BytesFwalkTests(FwalkTests):
|
|
"""Tests for os.walk() with bytes."""
|
|
def fwalk(self, top='.', *args, **kwargs):
|
|
for broot, bdirs, bfiles, topfd in os.fwalk(os.fsencode(top), *args, **kwargs):
|
|
root = os.fsdecode(broot)
|
|
dirs = list(map(os.fsdecode, bdirs))
|
|
files = list(map(os.fsdecode, bfiles))
|
|
yield (root, dirs, files, topfd)
|
|
bdirs[:] = list(map(os.fsencode, dirs))
|
|
bfiles[:] = list(map(os.fsencode, files))
|
|
|
|
|
|
class MakedirTests(unittest.TestCase):
|
|
def setUp(self):
|
|
os.mkdir(os_helper.TESTFN)
|
|
|
|
def test_makedir(self):
|
|
base = os_helper.TESTFN
|
|
path = os.path.join(base, 'dir1', 'dir2', 'dir3')
|
|
os.makedirs(path) # Should work
|
|
path = os.path.join(base, 'dir1', 'dir2', 'dir3', 'dir4')
|
|
os.makedirs(path)
|
|
|
|
# Try paths with a '.' in them
|
|
self.assertRaises(OSError, os.makedirs, os.curdir)
|
|
path = os.path.join(base, 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', os.curdir)
|
|
os.makedirs(path)
|
|
path = os.path.join(base, 'dir1', os.curdir, 'dir2', 'dir3', 'dir4',
|
|
'dir5', 'dir6')
|
|
os.makedirs(path)
|
|
|
|
@unittest.skipIf(
|
|
support.is_emscripten or support.is_wasi,
|
|
"Emscripten's/WASI's umask is a stub."
|
|
)
|
|
def test_mode(self):
|
|
with os_helper.temp_umask(0o002):
|
|
base = os_helper.TESTFN
|
|
parent = os.path.join(base, 'dir1')
|
|
path = os.path.join(parent, 'dir2')
|
|
os.makedirs(path, 0o555)
|
|
self.assertTrue(os.path.exists(path))
|
|
self.assertTrue(os.path.isdir(path))
|
|
if os.name != 'nt':
|
|
self.assertEqual(os.stat(path).st_mode & 0o777, 0o555)
|
|
self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775)
|
|
|
|
@unittest.skipIf(
|
|
support.is_emscripten or support.is_wasi,
|
|
"Emscripten's/WASI's umask is a stub."
|
|
)
|
|
def test_exist_ok_existing_directory(self):
|
|
path = os.path.join(os_helper.TESTFN, 'dir1')
|
|
mode = 0o777
|
|
old_mask = os.umask(0o022)
|
|
os.makedirs(path, mode)
|
|
self.assertRaises(OSError, os.makedirs, path, mode)
|
|
self.assertRaises(OSError, os.makedirs, path, mode, exist_ok=False)
|
|
os.makedirs(path, 0o776, exist_ok=True)
|
|
os.makedirs(path, mode=mode, exist_ok=True)
|
|
os.umask(old_mask)
|
|
|
|
# Issue #25583: A drive root could raise PermissionError on Windows
|
|
os.makedirs(os.path.abspath('/'), exist_ok=True)
|
|
|
|
@unittest.skipIf(
|
|
support.is_emscripten or support.is_wasi,
|
|
"Emscripten's/WASI's umask is a stub."
|
|
)
|
|
def test_exist_ok_s_isgid_directory(self):
|
|
path = os.path.join(os_helper.TESTFN, 'dir1')
|
|
S_ISGID = stat.S_ISGID
|
|
mode = 0o777
|
|
old_mask = os.umask(0o022)
|
|
try:
|
|
existing_testfn_mode = stat.S_IMODE(
|
|
os.lstat(os_helper.TESTFN).st_mode)
|
|
try:
|
|
os.chmod(os_helper.TESTFN, existing_testfn_mode | S_ISGID)
|
|
except PermissionError:
|
|
raise unittest.SkipTest('Cannot set S_ISGID for dir.')
|
|
if (os.lstat(os_helper.TESTFN).st_mode & S_ISGID != S_ISGID):
|
|
raise unittest.SkipTest('No support for S_ISGID dir mode.')
|
|
# The os should apply S_ISGID from the parent dir for us, but
|
|
# this test need not depend on that behavior. Be explicit.
|
|
os.makedirs(path, mode | S_ISGID)
|
|
# http://bugs.python.org/issue14992
|
|
# Should not fail when the bit is already set.
|
|
os.makedirs(path, mode, exist_ok=True)
|
|
# remove the bit.
|
|
os.chmod(path, stat.S_IMODE(os.lstat(path).st_mode) & ~S_ISGID)
|
|
# May work even when the bit is not already set when demanded.
|
|
os.makedirs(path, mode | S_ISGID, exist_ok=True)
|
|
finally:
|
|
os.umask(old_mask)
|
|
|
|
def test_exist_ok_existing_regular_file(self):
|
|
base = os_helper.TESTFN
|
|
path = os.path.join(os_helper.TESTFN, 'dir1')
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write('abc')
|
|
self.assertRaises(OSError, os.makedirs, path)
|
|
self.assertRaises(OSError, os.makedirs, path, exist_ok=False)
|
|
self.assertRaises(OSError, os.makedirs, path, exist_ok=True)
|
|
os.remove(path)
|
|
|
|
def tearDown(self):
|
|
path = os.path.join(os_helper.TESTFN, 'dir1', 'dir2', 'dir3',
|
|
'dir4', 'dir5', 'dir6')
|
|
# If the tests failed, the bottom-most directory ('../dir6')
|
|
# may not have been created, so we look for the outermost directory
|
|
# that exists.
|
|
while not os.path.exists(path) and path != os_helper.TESTFN:
|
|
path = os.path.dirname(path)
|
|
|
|
os.removedirs(path)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()")
|
|
class ChownFileTests(unittest.TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
os.mkdir(os_helper.TESTFN)
|
|
|
|
def test_chown_uid_gid_arguments_must_be_index(self):
|
|
stat = os.stat(os_helper.TESTFN)
|
|
uid = stat.st_uid
|
|
gid = stat.st_gid
|
|
for value in (-1.0, -1j, decimal.Decimal(-1), fractions.Fraction(-2, 2)):
|
|
self.assertRaises(TypeError, os.chown, os_helper.TESTFN, value, gid)
|
|
self.assertRaises(TypeError, os.chown, os_helper.TESTFN, uid, value)
|
|
self.assertIsNone(os.chown(os_helper.TESTFN, uid, gid))
|
|
self.assertIsNone(os.chown(os_helper.TESTFN, -1, -1))
|
|
|
|
@unittest.skipUnless(hasattr(os, 'getgroups'), 'need os.getgroups')
|
|
def test_chown_gid(self):
|
|
groups = os.getgroups()
|
|
if len(groups) < 2:
|
|
self.skipTest("test needs at least 2 groups")
|
|
|
|
gid_1, gid_2 = groups[:2]
|
|
uid = os.stat(os_helper.TESTFN).st_uid
|
|
|
|
os.chown(os_helper.TESTFN, uid, gid_1)
|
|
gid = os.stat(os_helper.TESTFN).st_gid
|
|
self.assertEqual(gid, gid_1)
|
|
|
|
os.chown(os_helper.TESTFN, uid, gid_2)
|
|
gid = os.stat(os_helper.TESTFN).st_gid
|
|
self.assertEqual(gid, gid_2)
|
|
|
|
@unittest.skipUnless(root_in_posix and len(all_users) > 1,
|
|
"test needs root privilege and more than one user")
|
|
def test_chown_with_root(self):
|
|
uid_1, uid_2 = all_users[:2]
|
|
gid = os.stat(os_helper.TESTFN).st_gid
|
|
os.chown(os_helper.TESTFN, uid_1, gid)
|
|
uid = os.stat(os_helper.TESTFN).st_uid
|
|
self.assertEqual(uid, uid_1)
|
|
os.chown(os_helper.TESTFN, uid_2, gid)
|
|
uid = os.stat(os_helper.TESTFN).st_uid
|
|
self.assertEqual(uid, uid_2)
|
|
|
|
@unittest.skipUnless(not root_in_posix and len(all_users) > 1,
|
|
"test needs non-root account and more than one user")
|
|
def test_chown_without_permission(self):
|
|
uid_1, uid_2 = all_users[:2]
|
|
gid = os.stat(os_helper.TESTFN).st_gid
|
|
with self.assertRaises(PermissionError):
|
|
os.chown(os_helper.TESTFN, uid_1, gid)
|
|
os.chown(os_helper.TESTFN, uid_2, gid)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
os.rmdir(os_helper.TESTFN)
|
|
|
|
|
|
class RemoveDirsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
os.makedirs(os_helper.TESTFN)
|
|
|
|
def tearDown(self):
|
|
os_helper.rmtree(os_helper.TESTFN)
|
|
|
|
def test_remove_all(self):
|
|
dira = os.path.join(os_helper.TESTFN, 'dira')
|
|
os.mkdir(dira)
|
|
dirb = os.path.join(dira, 'dirb')
|
|
os.mkdir(dirb)
|
|
os.removedirs(dirb)
|
|
self.assertFalse(os.path.exists(dirb))
|
|
self.assertFalse(os.path.exists(dira))
|
|
self.assertFalse(os.path.exists(os_helper.TESTFN))
|
|
|
|
def test_remove_partial(self):
|
|
dira = os.path.join(os_helper.TESTFN, 'dira')
|
|
os.mkdir(dira)
|
|
dirb = os.path.join(dira, 'dirb')
|
|
os.mkdir(dirb)
|
|
create_file(os.path.join(dira, 'file.txt'))
|
|
os.removedirs(dirb)
|
|
self.assertFalse(os.path.exists(dirb))
|
|
self.assertTrue(os.path.exists(dira))
|
|
self.assertTrue(os.path.exists(os_helper.TESTFN))
|
|
|
|
def test_remove_nothing(self):
|
|
dira = os.path.join(os_helper.TESTFN, 'dira')
|
|
os.mkdir(dira)
|
|
dirb = os.path.join(dira, 'dirb')
|
|
os.mkdir(dirb)
|
|
create_file(os.path.join(dirb, 'file.txt'))
|
|
with self.assertRaises(OSError):
|
|
os.removedirs(dirb)
|
|
self.assertTrue(os.path.exists(dirb))
|
|
self.assertTrue(os.path.exists(dira))
|
|
self.assertTrue(os.path.exists(os_helper.TESTFN))
|
|
|
|
|
|
@unittest.skipIf(support.is_wasi, "WASI has no /dev/null")
|
|
class DevNullTests(unittest.TestCase):
|
|
def test_devnull(self):
|
|
with open(os.devnull, 'wb', 0) as f:
|
|
f.write(b'hello')
|
|
f.close()
|
|
with open(os.devnull, 'rb') as f:
|
|
self.assertEqual(f.read(), b'')
|
|
|
|
|
|
class URandomTests(unittest.TestCase):
|
|
def test_urandom_length(self):
|
|
self.assertEqual(len(os.urandom(0)), 0)
|
|
self.assertEqual(len(os.urandom(1)), 1)
|
|
self.assertEqual(len(os.urandom(10)), 10)
|
|
self.assertEqual(len(os.urandom(100)), 100)
|
|
self.assertEqual(len(os.urandom(1000)), 1000)
|
|
|
|
def test_urandom_value(self):
|
|
data1 = os.urandom(16)
|
|
self.assertIsInstance(data1, bytes)
|
|
data2 = os.urandom(16)
|
|
self.assertNotEqual(data1, data2)
|
|
|
|
def get_urandom_subprocess(self, count):
|
|
code = '\n'.join((
|
|
'import os, sys',
|
|
'data = os.urandom(%s)' % count,
|
|
'sys.stdout.buffer.write(data)',
|
|
'sys.stdout.buffer.flush()'))
|
|
out = assert_python_ok('-c', code)
|
|
stdout = out[1]
|
|
self.assertEqual(len(stdout), count)
|
|
return stdout
|
|
|
|
def test_urandom_subprocess(self):
|
|
data1 = self.get_urandom_subprocess(16)
|
|
data2 = self.get_urandom_subprocess(16)
|
|
self.assertNotEqual(data1, data2)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'getrandom'), 'need os.getrandom()')
|
|
class GetRandomTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
try:
|
|
os.getrandom(1)
|
|
except OSError as exc:
|
|
if exc.errno == errno.ENOSYS:
|
|
# Python compiled on a more recent Linux version
|
|
# than the current Linux kernel
|
|
raise unittest.SkipTest("getrandom() syscall fails with ENOSYS")
|
|
else:
|
|
raise
|
|
|
|
def test_getrandom_type(self):
|
|
data = os.getrandom(16)
|
|
self.assertIsInstance(data, bytes)
|
|
self.assertEqual(len(data), 16)
|
|
|
|
def test_getrandom0(self):
|
|
empty = os.getrandom(0)
|
|
self.assertEqual(empty, b'')
|
|
|
|
def test_getrandom_random(self):
|
|
self.assertTrue(hasattr(os, 'GRND_RANDOM'))
|
|
|
|
# Don't test os.getrandom(1, os.GRND_RANDOM) to not consume the rare
|
|
# resource /dev/random
|
|
|
|
def test_getrandom_nonblock(self):
|
|
# The call must not fail. Check also that the flag exists
|
|
try:
|
|
os.getrandom(1, os.GRND_NONBLOCK)
|
|
except BlockingIOError:
|
|
# System urandom is not initialized yet
|
|
pass
|
|
|
|
def test_getrandom_value(self):
|
|
data1 = os.getrandom(16)
|
|
data2 = os.getrandom(16)
|
|
self.assertNotEqual(data1, data2)
|
|
|
|
|
|
# os.urandom() doesn't use a file descriptor when it is implemented with the
|
|
# getentropy() function, the getrandom() function or the getrandom() syscall
|
|
OS_URANDOM_DONT_USE_FD = (
|
|
sysconfig.get_config_var('HAVE_GETENTROPY') == 1
|
|
or sysconfig.get_config_var('HAVE_GETRANDOM') == 1
|
|
or sysconfig.get_config_var('HAVE_GETRANDOM_SYSCALL') == 1)
|
|
|
|
@unittest.skipIf(OS_URANDOM_DONT_USE_FD ,
|
|
"os.random() does not use a file descriptor")
|
|
@unittest.skipIf(sys.platform == "vxworks",
|
|
"VxWorks can't set RLIMIT_NOFILE to 1")
|
|
class URandomFDTests(unittest.TestCase):
|
|
@unittest.skipUnless(resource, "test requires the resource module")
|
|
def test_urandom_failure(self):
|
|
# Check urandom() failing when it is not able to open /dev/random.
|
|
# We spawn a new process to make the test more robust (if getrlimit()
|
|
# failed to restore the file descriptor limit after this, the whole
|
|
# test suite would crash; this actually happened on the OS X Tiger
|
|
# buildbot).
|
|
code = """if 1:
|
|
import errno
|
|
import os
|
|
import resource
|
|
|
|
soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
resource.setrlimit(resource.RLIMIT_NOFILE, (1, hard_limit))
|
|
try:
|
|
os.urandom(16)
|
|
except OSError as e:
|
|
assert e.errno == errno.EMFILE, e.errno
|
|
else:
|
|
raise AssertionError("OSError not raised")
|
|
"""
|
|
assert_python_ok('-c', code)
|
|
|
|
def test_urandom_fd_closed(self):
|
|
# Issue #21207: urandom() should reopen its fd to /dev/urandom if
|
|
# closed.
|
|
code = """if 1:
|
|
import os
|
|
import sys
|
|
import test.support
|
|
os.urandom(4)
|
|
with test.support.SuppressCrashReport():
|
|
os.closerange(3, 256)
|
|
sys.stdout.buffer.write(os.urandom(4))
|
|
"""
|
|
rc, out, err = assert_python_ok('-Sc', code)
|
|
|
|
def test_urandom_fd_reopened(self):
|
|
# Issue #21207: urandom() should detect its fd to /dev/urandom
|
|
# changed to something else, and reopen it.
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
create_file(os_helper.TESTFN, b"x" * 256)
|
|
|
|
code = """if 1:
|
|
import os
|
|
import sys
|
|
import test.support
|
|
os.urandom(4)
|
|
with test.support.SuppressCrashReport():
|
|
for fd in range(3, 256):
|
|
try:
|
|
os.close(fd)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
# Found the urandom fd (XXX hopefully)
|
|
break
|
|
os.closerange(3, 256)
|
|
with open({TESTFN!r}, 'rb') as f:
|
|
new_fd = f.fileno()
|
|
# Issue #26935: posix allows new_fd and fd to be equal but
|
|
# some libc implementations have dup2 return an error in this
|
|
# case.
|
|
if new_fd != fd:
|
|
os.dup2(new_fd, fd)
|
|
sys.stdout.buffer.write(os.urandom(4))
|
|
sys.stdout.buffer.write(os.urandom(4))
|
|
""".format(TESTFN=os_helper.TESTFN)
|
|
rc, out, err = assert_python_ok('-Sc', code)
|
|
self.assertEqual(len(out), 8)
|
|
self.assertNotEqual(out[0:4], out[4:8])
|
|
rc, out2, err2 = assert_python_ok('-Sc', code)
|
|
self.assertEqual(len(out2), 8)
|
|
self.assertNotEqual(out2, out)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _execvpe_mockup(defpath=None):
|
|
"""
|
|
Stubs out execv and execve functions when used as context manager.
|
|
Records exec calls. The mock execv and execve functions always raise an
|
|
exception as they would normally never return.
|
|
"""
|
|
# A list of tuples containing (function name, first arg, args)
|
|
# of calls to execv or execve that have been made.
|
|
calls = []
|
|
|
|
def mock_execv(name, *args):
|
|
calls.append(('execv', name, args))
|
|
raise RuntimeError("execv called")
|
|
|
|
def mock_execve(name, *args):
|
|
calls.append(('execve', name, args))
|
|
raise OSError(errno.ENOTDIR, "execve called")
|
|
|
|
try:
|
|
orig_execv = os.execv
|
|
orig_execve = os.execve
|
|
orig_defpath = os.defpath
|
|
os.execv = mock_execv
|
|
os.execve = mock_execve
|
|
if defpath is not None:
|
|
os.defpath = defpath
|
|
yield calls
|
|
finally:
|
|
os.execv = orig_execv
|
|
os.execve = orig_execve
|
|
os.defpath = orig_defpath
|
|
|
|
@unittest.skipUnless(hasattr(os, 'execv'),
|
|
"need os.execv()")
|
|
class ExecTests(unittest.TestCase):
|
|
@unittest.skipIf(USING_LINUXTHREADS,
|
|
"avoid triggering a linuxthreads bug: see issue #4970")
|
|
def test_execvpe_with_bad_program(self):
|
|
self.assertRaises(OSError, os.execvpe, 'no such app-',
|
|
['no such app-'], None)
|
|
|
|
def test_execv_with_bad_arglist(self):
|
|
self.assertRaises(ValueError, os.execv, 'notepad', ())
|
|
self.assertRaises(ValueError, os.execv, 'notepad', [])
|
|
self.assertRaises(ValueError, os.execv, 'notepad', ('',))
|
|
self.assertRaises(ValueError, os.execv, 'notepad', [''])
|
|
|
|
def test_execvpe_with_bad_arglist(self):
|
|
self.assertRaises(ValueError, os.execvpe, 'notepad', [], None)
|
|
self.assertRaises(ValueError, os.execvpe, 'notepad', [], {})
|
|
self.assertRaises(ValueError, os.execvpe, 'notepad', [''], {})
|
|
|
|
@unittest.skipUnless(hasattr(os, '_execvpe'),
|
|
"No internal os._execvpe function to test.")
|
|
def _test_internal_execvpe(self, test_type):
|
|
program_path = os.sep + 'absolutepath'
|
|
if test_type is bytes:
|
|
program = b'executable'
|
|
fullpath = os.path.join(os.fsencode(program_path), program)
|
|
native_fullpath = fullpath
|
|
arguments = [b'progname', 'arg1', 'arg2']
|
|
else:
|
|
program = 'executable'
|
|
arguments = ['progname', 'arg1', 'arg2']
|
|
fullpath = os.path.join(program_path, program)
|
|
if os.name != "nt":
|
|
native_fullpath = os.fsencode(fullpath)
|
|
else:
|
|
native_fullpath = fullpath
|
|
env = {'spam': 'beans'}
|
|
|
|
# test os._execvpe() with an absolute path
|
|
with _execvpe_mockup() as calls:
|
|
self.assertRaises(RuntimeError,
|
|
os._execvpe, fullpath, arguments)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertEqual(calls[0], ('execv', fullpath, (arguments,)))
|
|
|
|
# test os._execvpe() with a relative path:
|
|
# os.get_exec_path() returns defpath
|
|
with _execvpe_mockup(defpath=program_path) as calls:
|
|
self.assertRaises(OSError,
|
|
os._execvpe, program, arguments, env=env)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertSequenceEqual(calls[0],
|
|
('execve', native_fullpath, (arguments, env)))
|
|
|
|
# test os._execvpe() with a relative path:
|
|
# os.get_exec_path() reads the 'PATH' variable
|
|
with _execvpe_mockup() as calls:
|
|
env_path = env.copy()
|
|
if test_type is bytes:
|
|
env_path[b'PATH'] = program_path
|
|
else:
|
|
env_path['PATH'] = program_path
|
|
self.assertRaises(OSError,
|
|
os._execvpe, program, arguments, env=env_path)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertSequenceEqual(calls[0],
|
|
('execve', native_fullpath, (arguments, env_path)))
|
|
|
|
def test_internal_execvpe_str(self):
|
|
self._test_internal_execvpe(str)
|
|
if os.name != "nt":
|
|
self._test_internal_execvpe(bytes)
|
|
|
|
def test_execve_invalid_env(self):
|
|
args = [sys.executable, '-c', 'pass']
|
|
|
|
# null character in the environment variable name
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT\0VEGETABLE"] = "cabbage"
|
|
with self.assertRaises(ValueError):
|
|
os.execve(args[0], args, newenv)
|
|
|
|
# null character in the environment variable value
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
|
|
with self.assertRaises(ValueError):
|
|
os.execve(args[0], args, newenv)
|
|
|
|
# equal character in the environment variable name
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT=ORANGE"] = "lemon"
|
|
with self.assertRaises(ValueError):
|
|
os.execve(args[0], args, newenv)
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32-specific test")
|
|
def test_execve_with_empty_path(self):
|
|
# bpo-32890: Check GetLastError() misuse
|
|
try:
|
|
os.execve('', ['arg'], {})
|
|
except OSError as e:
|
|
self.assertTrue(e.winerror is None or e.winerror != 0)
|
|
else:
|
|
self.fail('No OSError raised')
|
|
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
class Win32ErrorTests(unittest.TestCase):
|
|
def setUp(self):
|
|
try:
|
|
os.stat(os_helper.TESTFN)
|
|
except FileNotFoundError:
|
|
exists = False
|
|
except OSError as exc:
|
|
exists = True
|
|
self.fail("file %s must not exist; os.stat failed with %s"
|
|
% (os_helper.TESTFN, exc))
|
|
else:
|
|
self.fail("file %s must not exist" % os_helper.TESTFN)
|
|
|
|
def test_rename(self):
|
|
self.assertRaises(OSError, os.rename, os_helper.TESTFN, os_helper.TESTFN+".bak")
|
|
|
|
def test_remove(self):
|
|
self.assertRaises(OSError, os.remove, os_helper.TESTFN)
|
|
|
|
def test_chdir(self):
|
|
self.assertRaises(OSError, os.chdir, os_helper.TESTFN)
|
|
|
|
def test_mkdir(self):
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
|
|
with open(os_helper.TESTFN, "x") as f:
|
|
self.assertRaises(OSError, os.mkdir, os_helper.TESTFN)
|
|
|
|
def test_utime(self):
|
|
self.assertRaises(OSError, os.utime, os_helper.TESTFN, None)
|
|
|
|
def test_chmod(self):
|
|
self.assertRaises(OSError, os.chmod, os_helper.TESTFN, 0)
|
|
|
|
|
|
@unittest.skipIf(support.is_wasi, "Cannot create invalid FD on WASI.")
|
|
class TestInvalidFD(unittest.TestCase):
|
|
singles = ["fchdir", "dup", "fdatasync", "fstat",
|
|
"fstatvfs", "fsync", "tcgetpgrp", "ttyname"]
|
|
singles_fildes = {"fchdir", "fdatasync", "fsync"}
|
|
#singles.append("close")
|
|
#We omit close because it doesn't raise an exception on some platforms
|
|
def get_single(f):
|
|
def helper(self):
|
|
if hasattr(os, f):
|
|
self.check(getattr(os, f))
|
|
if f in self.singles_fildes:
|
|
self.check_bool(getattr(os, f))
|
|
return helper
|
|
for f in singles:
|
|
locals()["test_"+f] = get_single(f)
|
|
|
|
def check(self, f, *args, **kwargs):
|
|
try:
|
|
f(os_helper.make_bad_fd(), *args, **kwargs)
|
|
except OSError as e:
|
|
self.assertEqual(e.errno, errno.EBADF)
|
|
else:
|
|
self.fail("%r didn't raise an OSError with a bad file descriptor"
|
|
% f)
|
|
|
|
def check_bool(self, f, *args, **kwargs):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error", RuntimeWarning)
|
|
for fd in False, True:
|
|
with self.assertRaises(RuntimeWarning):
|
|
f(fd, *args, **kwargs)
|
|
|
|
def test_fdopen(self):
|
|
self.check(os.fdopen, encoding="utf-8")
|
|
self.check_bool(os.fdopen, encoding="utf-8")
|
|
|
|
@unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()')
|
|
def test_isatty(self):
|
|
self.assertEqual(os.isatty(os_helper.make_bad_fd()), False)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'closerange'), 'test needs os.closerange()')
|
|
def test_closerange(self):
|
|
fd = os_helper.make_bad_fd()
|
|
# Make sure none of the descriptors we are about to close are
|
|
# currently valid (issue 6542).
|
|
for i in range(10):
|
|
try: os.fstat(fd+i)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
break
|
|
if i < 2:
|
|
raise unittest.SkipTest(
|
|
"Unable to acquire a range of invalid file descriptors")
|
|
self.assertEqual(os.closerange(fd, fd + i-1), None)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()')
|
|
def test_dup2(self):
|
|
self.check(os.dup2, 20)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()')
|
|
@unittest.skipIf(
|
|
support.is_emscripten,
|
|
"dup2() with negative fds is broken on Emscripten (see gh-102179)"
|
|
)
|
|
def test_dup2_negative_fd(self):
|
|
valid_fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, valid_fd)
|
|
fds = [
|
|
valid_fd,
|
|
-1,
|
|
-2**31,
|
|
]
|
|
for fd, fd2 in itertools.product(fds, repeat=2):
|
|
if fd != fd2:
|
|
with self.subTest(fd=fd, fd2=fd2):
|
|
with self.assertRaises(OSError) as ctx:
|
|
os.dup2(fd, fd2)
|
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'fchmod'), 'test needs os.fchmod()')
|
|
def test_fchmod(self):
|
|
self.check(os.fchmod, 0)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'fchown'), 'test needs os.fchown()')
|
|
def test_fchown(self):
|
|
self.check(os.fchown, -1, -1)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
|
|
@unittest.skipIf(
|
|
support.is_emscripten or support.is_wasi,
|
|
"musl libc issue on Emscripten/WASI, bpo-46390"
|
|
)
|
|
def test_fpathconf(self):
|
|
self.check(os.pathconf, "PC_NAME_MAX")
|
|
self.check(os.fpathconf, "PC_NAME_MAX")
|
|
self.check_bool(os.pathconf, "PC_NAME_MAX")
|
|
self.check_bool(os.fpathconf, "PC_NAME_MAX")
|
|
|
|
@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()')
|
|
def test_ftruncate(self):
|
|
self.check(os.truncate, 0)
|
|
self.check(os.ftruncate, 0)
|
|
self.check_bool(os.truncate, 0)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()')
|
|
def test_lseek(self):
|
|
self.check(os.lseek, 0, 0)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()')
|
|
def test_read(self):
|
|
self.check(os.read, 1)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'readv'), 'test needs os.readv()')
|
|
def test_readv(self):
|
|
buf = bytearray(10)
|
|
self.check(os.readv, [buf])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'tcsetpgrp'), 'test needs os.tcsetpgrp()')
|
|
def test_tcsetpgrpt(self):
|
|
self.check(os.tcsetpgrp, 0)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()')
|
|
def test_write(self):
|
|
self.check(os.write, b" ")
|
|
|
|
@unittest.skipUnless(hasattr(os, 'writev'), 'test needs os.writev()')
|
|
def test_writev(self):
|
|
self.check(os.writev, [b'abc'])
|
|
|
|
@support.requires_subprocess()
|
|
def test_inheritable(self):
|
|
self.check(os.get_inheritable)
|
|
self.check(os.set_inheritable, True)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
|
'needs os.get_blocking() and os.set_blocking()')
|
|
def test_blocking(self):
|
|
self.check(os.get_blocking)
|
|
self.check(os.set_blocking, True)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
|
|
class LinkTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.file1 = os_helper.TESTFN
|
|
self.file2 = os.path.join(os_helper.TESTFN + "2")
|
|
|
|
def tearDown(self):
|
|
for file in (self.file1, self.file2):
|
|
if os.path.exists(file):
|
|
os.unlink(file)
|
|
|
|
def _test_link(self, file1, file2):
|
|
create_file(file1)
|
|
|
|
try:
|
|
os.link(file1, file2)
|
|
except PermissionError as e:
|
|
self.skipTest('os.link(): %s' % e)
|
|
with open(file1, "rb") as f1, open(file2, "rb") as f2:
|
|
self.assertTrue(os.path.sameopenfile(f1.fileno(), f2.fileno()))
|
|
|
|
def test_link(self):
|
|
self._test_link(self.file1, self.file2)
|
|
|
|
def test_link_bytes(self):
|
|
self._test_link(bytes(self.file1, sys.getfilesystemencoding()),
|
|
bytes(self.file2, sys.getfilesystemencoding()))
|
|
|
|
def test_unicode_name(self):
|
|
try:
|
|
os.fsencode("\xf1")
|
|
except UnicodeError:
|
|
raise unittest.SkipTest("Unable to encode for this platform.")
|
|
|
|
self.file1 += "\xf1"
|
|
self.file2 = self.file1 + "2"
|
|
self._test_link(self.file1, self.file2)
|
|
|
|
@unittest.skipIf(sys.platform == "win32", "Posix specific tests")
|
|
class PosixUidGidTests(unittest.TestCase):
|
|
# uid_t and gid_t are 32-bit unsigned integers on Linux
|
|
UID_OVERFLOW = (1 << 32)
|
|
GID_OVERFLOW = (1 << 32)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setuid'), 'test needs os.setuid()')
|
|
def test_setuid(self):
|
|
if os.getuid() != 0:
|
|
self.assertRaises(OSError, os.setuid, 0)
|
|
self.assertRaises(TypeError, os.setuid, 'not an int')
|
|
self.assertRaises(OverflowError, os.setuid, self.UID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()')
|
|
def test_setgid(self):
|
|
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
|
|
self.assertRaises(OSError, os.setgid, 0)
|
|
self.assertRaises(TypeError, os.setgid, 'not an int')
|
|
self.assertRaises(OverflowError, os.setgid, self.GID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()')
|
|
def test_seteuid(self):
|
|
if os.getuid() != 0:
|
|
self.assertRaises(OSError, os.seteuid, 0)
|
|
self.assertRaises(TypeError, os.setegid, 'not an int')
|
|
self.assertRaises(OverflowError, os.seteuid, self.UID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()')
|
|
def test_setegid(self):
|
|
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
|
|
self.assertRaises(OSError, os.setegid, 0)
|
|
self.assertRaises(TypeError, os.setegid, 'not an int')
|
|
self.assertRaises(OverflowError, os.setegid, self.GID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()')
|
|
def test_setreuid(self):
|
|
if os.getuid() != 0:
|
|
self.assertRaises(OSError, os.setreuid, 0, 0)
|
|
self.assertRaises(TypeError, os.setreuid, 'not an int', 0)
|
|
self.assertRaises(TypeError, os.setreuid, 0, 'not an int')
|
|
self.assertRaises(OverflowError, os.setreuid, self.UID_OVERFLOW, 0)
|
|
self.assertRaises(OverflowError, os.setreuid, 0, self.UID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()')
|
|
@support.requires_subprocess()
|
|
def test_setreuid_neg1(self):
|
|
# Needs to accept -1. We run this in a subprocess to avoid
|
|
# altering the test runner's process state (issue8045).
|
|
subprocess.check_call([
|
|
sys.executable, '-c',
|
|
'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()')
|
|
@support.requires_subprocess()
|
|
def test_setregid(self):
|
|
if os.getuid() != 0 and not HAVE_WHEEL_GROUP:
|
|
self.assertRaises(OSError, os.setregid, 0, 0)
|
|
self.assertRaises(TypeError, os.setregid, 'not an int', 0)
|
|
self.assertRaises(TypeError, os.setregid, 0, 'not an int')
|
|
self.assertRaises(OverflowError, os.setregid, self.GID_OVERFLOW, 0)
|
|
self.assertRaises(OverflowError, os.setregid, 0, self.GID_OVERFLOW)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'setregid'), 'test needs os.setregid()')
|
|
@support.requires_subprocess()
|
|
def test_setregid_neg1(self):
|
|
# Needs to accept -1. We run this in a subprocess to avoid
|
|
# altering the test runner's process state (issue8045).
|
|
subprocess.check_call([
|
|
sys.executable, '-c',
|
|
'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
|
|
|
|
@unittest.skipIf(sys.platform == "win32", "Posix specific tests")
|
|
class Pep383Tests(unittest.TestCase):
|
|
def setUp(self):
|
|
if os_helper.TESTFN_UNENCODABLE:
|
|
self.dir = os_helper.TESTFN_UNENCODABLE
|
|
elif os_helper.TESTFN_NONASCII:
|
|
self.dir = os_helper.TESTFN_NONASCII
|
|
else:
|
|
self.dir = os_helper.TESTFN
|
|
self.bdir = os.fsencode(self.dir)
|
|
|
|
bytesfn = []
|
|
def add_filename(fn):
|
|
try:
|
|
fn = os.fsencode(fn)
|
|
except UnicodeEncodeError:
|
|
return
|
|
bytesfn.append(fn)
|
|
add_filename(os_helper.TESTFN_UNICODE)
|
|
if os_helper.TESTFN_UNENCODABLE:
|
|
add_filename(os_helper.TESTFN_UNENCODABLE)
|
|
if os_helper.TESTFN_NONASCII:
|
|
add_filename(os_helper.TESTFN_NONASCII)
|
|
if not bytesfn:
|
|
self.skipTest("couldn't create any non-ascii filename")
|
|
|
|
self.unicodefn = set()
|
|
os.mkdir(self.dir)
|
|
try:
|
|
for fn in bytesfn:
|
|
os_helper.create_empty_file(os.path.join(self.bdir, fn))
|
|
fn = os.fsdecode(fn)
|
|
if fn in self.unicodefn:
|
|
raise ValueError("duplicate filename")
|
|
self.unicodefn.add(fn)
|
|
except:
|
|
shutil.rmtree(self.dir)
|
|
raise
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.dir)
|
|
|
|
def test_listdir(self):
|
|
expected = self.unicodefn
|
|
found = set(os.listdir(self.dir))
|
|
self.assertEqual(found, expected)
|
|
# test listdir without arguments
|
|
current_directory = os.getcwd()
|
|
try:
|
|
# The root directory is not readable on Android, so use a directory
|
|
# we created ourselves.
|
|
os.chdir(self.dir)
|
|
self.assertEqual(set(os.listdir()), expected)
|
|
finally:
|
|
os.chdir(current_directory)
|
|
|
|
def test_open(self):
|
|
for fn in self.unicodefn:
|
|
f = open(os.path.join(self.dir, fn), 'rb')
|
|
f.close()
|
|
|
|
@unittest.skipUnless(hasattr(os, 'statvfs'),
|
|
"need os.statvfs()")
|
|
def test_statvfs(self):
|
|
# issue #9645
|
|
for fn in self.unicodefn:
|
|
# should not fail with file not found error
|
|
fullname = os.path.join(self.dir, fn)
|
|
os.statvfs(fullname)
|
|
|
|
def test_stat(self):
|
|
for fn in self.unicodefn:
|
|
os.stat(os.path.join(self.dir, fn))
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
class Win32KillTests(unittest.TestCase):
|
|
def _kill(self, sig):
|
|
# Start sys.executable as a subprocess and communicate from the
|
|
# subprocess to the parent that the interpreter is ready. When it
|
|
# becomes ready, send *sig* via os.kill to the subprocess and check
|
|
# that the return code is equal to *sig*.
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
import msvcrt
|
|
|
|
# Since we can't access the contents of the process' stdout until the
|
|
# process has exited, use PeekNamedPipe to see what's inside stdout
|
|
# without waiting. This is done so we can tell that the interpreter
|
|
# is started and running at a point where it could handle a signal.
|
|
PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe
|
|
PeekNamedPipe.restype = wintypes.BOOL
|
|
PeekNamedPipe.argtypes = (wintypes.HANDLE, # Pipe handle
|
|
ctypes.POINTER(ctypes.c_char), # stdout buf
|
|
wintypes.DWORD, # Buffer size
|
|
ctypes.POINTER(wintypes.DWORD), # bytes read
|
|
ctypes.POINTER(wintypes.DWORD), # bytes avail
|
|
ctypes.POINTER(wintypes.DWORD)) # bytes left
|
|
msg = "running"
|
|
proc = subprocess.Popen([sys.executable, "-c",
|
|
"import sys;"
|
|
"sys.stdout.write('{}');"
|
|
"sys.stdout.flush();"
|
|
"input()".format(msg)],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
stdin=subprocess.PIPE)
|
|
self.addCleanup(proc.stdout.close)
|
|
self.addCleanup(proc.stderr.close)
|
|
self.addCleanup(proc.stdin.close)
|
|
|
|
count, max = 0, 100
|
|
while count < max and proc.poll() is None:
|
|
# Create a string buffer to store the result of stdout from the pipe
|
|
buf = ctypes.create_string_buffer(len(msg))
|
|
# Obtain the text currently in proc.stdout
|
|
# Bytes read/avail/left are left as NULL and unused
|
|
rslt = PeekNamedPipe(msvcrt.get_osfhandle(proc.stdout.fileno()),
|
|
buf, ctypes.sizeof(buf), None, None, None)
|
|
self.assertNotEqual(rslt, 0, "PeekNamedPipe failed")
|
|
if buf.value:
|
|
self.assertEqual(msg, buf.value.decode())
|
|
break
|
|
time.sleep(0.1)
|
|
count += 1
|
|
else:
|
|
self.fail("Did not receive communication from the subprocess")
|
|
|
|
os.kill(proc.pid, sig)
|
|
self.assertEqual(proc.wait(), sig)
|
|
|
|
def test_kill_sigterm(self):
|
|
# SIGTERM doesn't mean anything special, but make sure it works
|
|
self._kill(signal.SIGTERM)
|
|
|
|
def test_kill_int(self):
|
|
# os.kill on Windows can take an int which gets set as the exit code
|
|
self._kill(100)
|
|
|
|
@unittest.skipIf(mmap is None, "requires mmap")
|
|
def _kill_with_event(self, event, name):
|
|
tagname = "test_os_%s" % uuid.uuid1()
|
|
m = mmap.mmap(-1, 1, tagname)
|
|
m[0] = 0
|
|
|
|
# Run a script which has console control handling enabled.
|
|
script = os.path.join(os.path.dirname(__file__),
|
|
"win_console_handler.py")
|
|
cmd = [sys.executable, script, tagname]
|
|
proc = subprocess.Popen(cmd,
|
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
|
|
|
with proc:
|
|
# Let the interpreter startup before we send signals. See #3137.
|
|
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
|
|
if proc.poll() is None:
|
|
break
|
|
else:
|
|
# Forcefully kill the process if we weren't able to signal it.
|
|
proc.kill()
|
|
self.fail("Subprocess didn't finish initialization")
|
|
|
|
os.kill(proc.pid, event)
|
|
|
|
try:
|
|
# proc.send_signal(event) could also be done here.
|
|
# Allow time for the signal to be passed and the process to exit.
|
|
proc.wait(timeout=support.SHORT_TIMEOUT)
|
|
except subprocess.TimeoutExpired:
|
|
# Forcefully kill the process if we weren't able to signal it.
|
|
proc.kill()
|
|
self.fail("subprocess did not stop on {}".format(name))
|
|
|
|
@unittest.skip("subprocesses aren't inheriting Ctrl+C property")
|
|
@support.requires_subprocess()
|
|
def test_CTRL_C_EVENT(self):
|
|
from ctypes import wintypes
|
|
import ctypes
|
|
|
|
# Make a NULL value by creating a pointer with no argument.
|
|
NULL = ctypes.POINTER(ctypes.c_int)()
|
|
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
|
|
SetConsoleCtrlHandler.argtypes = (ctypes.POINTER(ctypes.c_int),
|
|
wintypes.BOOL)
|
|
SetConsoleCtrlHandler.restype = wintypes.BOOL
|
|
|
|
# Calling this with NULL and FALSE causes the calling process to
|
|
# handle Ctrl+C, rather than ignore it. This property is inherited
|
|
# by subprocesses.
|
|
SetConsoleCtrlHandler(NULL, 0)
|
|
|
|
self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT")
|
|
|
|
@support.requires_subprocess()
|
|
def test_CTRL_BREAK_EVENT(self):
|
|
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
|
|
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
class Win32ListdirTests(unittest.TestCase):
|
|
"""Test listdir on Windows."""
|
|
|
|
def setUp(self):
|
|
self.created_paths = []
|
|
for i in range(2):
|
|
dir_name = 'SUB%d' % i
|
|
dir_path = os.path.join(os_helper.TESTFN, dir_name)
|
|
file_name = 'FILE%d' % i
|
|
file_path = os.path.join(os_helper.TESTFN, file_name)
|
|
os.makedirs(dir_path)
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write("I'm %s and proud of it. Blame test_os.\n" % file_path)
|
|
self.created_paths.extend([dir_name, file_name])
|
|
self.created_paths.sort()
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(os_helper.TESTFN)
|
|
|
|
def test_listdir_no_extended_path(self):
|
|
"""Test when the path is not an "extended" path."""
|
|
# unicode
|
|
self.assertEqual(
|
|
sorted(os.listdir(os_helper.TESTFN)),
|
|
self.created_paths)
|
|
|
|
# bytes
|
|
self.assertEqual(
|
|
sorted(os.listdir(os.fsencode(os_helper.TESTFN))),
|
|
[os.fsencode(path) for path in self.created_paths])
|
|
|
|
def test_listdir_extended_path(self):
|
|
"""Test when the path starts with '\\\\?\\'."""
|
|
# See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
|
|
# unicode
|
|
path = '\\\\?\\' + os.path.abspath(os_helper.TESTFN)
|
|
self.assertEqual(
|
|
sorted(os.listdir(path)),
|
|
self.created_paths)
|
|
|
|
# bytes
|
|
path = b'\\\\?\\' + os.fsencode(os.path.abspath(os_helper.TESTFN))
|
|
self.assertEqual(
|
|
sorted(os.listdir(path)),
|
|
[os.fsencode(path) for path in self.created_paths])
|
|
|
|
|
|
@unittest.skipUnless(os.name == "nt", "NT specific tests")
|
|
class Win32ListdriveTests(unittest.TestCase):
|
|
"""Test listdrive, listmounts and listvolume on Windows."""
|
|
|
|
def setUp(self):
|
|
# Get drives and volumes from fsutil
|
|
out = subprocess.check_output(
|
|
["fsutil.exe", "volume", "list"],
|
|
cwd=os.path.join(os.getenv("SystemRoot", "\\Windows"), "System32"),
|
|
encoding="mbcs",
|
|
errors="ignore",
|
|
)
|
|
lines = out.splitlines()
|
|
self.known_volumes = {l for l in lines if l.startswith('\\\\?\\')}
|
|
self.known_drives = {l for l in lines if l[1:] == ':\\'}
|
|
self.known_mounts = {l for l in lines if l[1:3] == ':\\'}
|
|
|
|
def test_listdrives(self):
|
|
drives = os.listdrives()
|
|
self.assertIsInstance(drives, list)
|
|
self.assertSetEqual(
|
|
self.known_drives,
|
|
self.known_drives & set(drives),
|
|
)
|
|
|
|
def test_listvolumes(self):
|
|
volumes = os.listvolumes()
|
|
self.assertIsInstance(volumes, list)
|
|
self.assertSetEqual(
|
|
self.known_volumes,
|
|
self.known_volumes & set(volumes),
|
|
)
|
|
|
|
def test_listmounts(self):
|
|
for volume in os.listvolumes():
|
|
try:
|
|
mounts = os.listmounts(volume)
|
|
except OSError as ex:
|
|
if support.verbose:
|
|
print("Skipping", volume, "because of", ex)
|
|
else:
|
|
self.assertIsInstance(mounts, list)
|
|
self.assertSetEqual(
|
|
set(mounts),
|
|
self.known_mounts & set(mounts),
|
|
)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'readlink'), 'needs os.readlink()')
|
|
class ReadlinkTests(unittest.TestCase):
|
|
filelink = 'readlinktest'
|
|
filelink_target = os.path.abspath(__file__)
|
|
filelinkb = os.fsencode(filelink)
|
|
filelinkb_target = os.fsencode(filelink_target)
|
|
|
|
def assertPathEqual(self, left, right):
|
|
left = os.path.normcase(left)
|
|
right = os.path.normcase(right)
|
|
if sys.platform == 'win32':
|
|
# Bad practice to blindly strip the prefix as it may be required to
|
|
# correctly refer to the file, but we're only comparing paths here.
|
|
has_prefix = lambda p: p.startswith(
|
|
b'\\\\?\\' if isinstance(p, bytes) else '\\\\?\\')
|
|
if has_prefix(left):
|
|
left = left[4:]
|
|
if has_prefix(right):
|
|
right = right[4:]
|
|
self.assertEqual(left, right)
|
|
|
|
def setUp(self):
|
|
self.assertTrue(os.path.exists(self.filelink_target))
|
|
self.assertTrue(os.path.exists(self.filelinkb_target))
|
|
self.assertFalse(os.path.exists(self.filelink))
|
|
self.assertFalse(os.path.exists(self.filelinkb))
|
|
|
|
def test_not_symlink(self):
|
|
filelink_target = FakePath(self.filelink_target)
|
|
self.assertRaises(OSError, os.readlink, self.filelink_target)
|
|
self.assertRaises(OSError, os.readlink, filelink_target)
|
|
|
|
def test_missing_link(self):
|
|
self.assertRaises(FileNotFoundError, os.readlink, 'missing-link')
|
|
self.assertRaises(FileNotFoundError, os.readlink,
|
|
FakePath('missing-link'))
|
|
|
|
@os_helper.skip_unless_symlink
|
|
def test_pathlike(self):
|
|
os.symlink(self.filelink_target, self.filelink)
|
|
self.addCleanup(os_helper.unlink, self.filelink)
|
|
filelink = FakePath(self.filelink)
|
|
self.assertPathEqual(os.readlink(filelink), self.filelink_target)
|
|
|
|
@os_helper.skip_unless_symlink
|
|
def test_pathlike_bytes(self):
|
|
os.symlink(self.filelinkb_target, self.filelinkb)
|
|
self.addCleanup(os_helper.unlink, self.filelinkb)
|
|
path = os.readlink(FakePath(self.filelinkb))
|
|
self.assertPathEqual(path, self.filelinkb_target)
|
|
self.assertIsInstance(path, bytes)
|
|
|
|
@os_helper.skip_unless_symlink
|
|
def test_bytes(self):
|
|
os.symlink(self.filelinkb_target, self.filelinkb)
|
|
self.addCleanup(os_helper.unlink, self.filelinkb)
|
|
path = os.readlink(self.filelinkb)
|
|
self.assertPathEqual(path, self.filelinkb_target)
|
|
self.assertIsInstance(path, bytes)
|
|
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
@os_helper.skip_unless_symlink
|
|
class Win32SymlinkTests(unittest.TestCase):
|
|
filelink = 'filelinktest'
|
|
filelink_target = os.path.abspath(__file__)
|
|
dirlink = 'dirlinktest'
|
|
dirlink_target = os.path.dirname(filelink_target)
|
|
missing_link = 'missing link'
|
|
|
|
def setUp(self):
|
|
assert os.path.exists(self.dirlink_target)
|
|
assert os.path.exists(self.filelink_target)
|
|
assert not os.path.exists(self.dirlink)
|
|
assert not os.path.exists(self.filelink)
|
|
assert not os.path.exists(self.missing_link)
|
|
|
|
def tearDown(self):
|
|
if os.path.exists(self.filelink):
|
|
os.remove(self.filelink)
|
|
if os.path.exists(self.dirlink):
|
|
os.rmdir(self.dirlink)
|
|
if os.path.lexists(self.missing_link):
|
|
os.remove(self.missing_link)
|
|
|
|
def test_directory_link(self):
|
|
os.symlink(self.dirlink_target, self.dirlink)
|
|
self.assertTrue(os.path.exists(self.dirlink))
|
|
self.assertTrue(os.path.isdir(self.dirlink))
|
|
self.assertTrue(os.path.islink(self.dirlink))
|
|
self.check_stat(self.dirlink, self.dirlink_target)
|
|
|
|
def test_file_link(self):
|
|
os.symlink(self.filelink_target, self.filelink)
|
|
self.assertTrue(os.path.exists(self.filelink))
|
|
self.assertTrue(os.path.isfile(self.filelink))
|
|
self.assertTrue(os.path.islink(self.filelink))
|
|
self.check_stat(self.filelink, self.filelink_target)
|
|
|
|
def _create_missing_dir_link(self):
|
|
'Create a "directory" link to a non-existent target'
|
|
linkname = self.missing_link
|
|
if os.path.lexists(linkname):
|
|
os.remove(linkname)
|
|
target = r'c:\\target does not exist.29r3c740'
|
|
assert not os.path.exists(target)
|
|
target_is_dir = True
|
|
os.symlink(target, linkname, target_is_dir)
|
|
|
|
def test_remove_directory_link_to_missing_target(self):
|
|
self._create_missing_dir_link()
|
|
# For compatibility with Unix, os.remove will check the
|
|
# directory status and call RemoveDirectory if the symlink
|
|
# was created with target_is_dir==True.
|
|
os.remove(self.missing_link)
|
|
|
|
def test_isdir_on_directory_link_to_missing_target(self):
|
|
self._create_missing_dir_link()
|
|
self.assertFalse(os.path.isdir(self.missing_link))
|
|
|
|
def test_rmdir_on_directory_link_to_missing_target(self):
|
|
self._create_missing_dir_link()
|
|
os.rmdir(self.missing_link)
|
|
|
|
def check_stat(self, link, target):
|
|
self.assertEqual(os.stat(link), os.stat(target))
|
|
self.assertNotEqual(os.lstat(link), os.stat(link))
|
|
|
|
bytes_link = os.fsencode(link)
|
|
self.assertEqual(os.stat(bytes_link), os.stat(target))
|
|
self.assertNotEqual(os.lstat(bytes_link), os.stat(bytes_link))
|
|
|
|
def test_12084(self):
|
|
level1 = os.path.abspath(os_helper.TESTFN)
|
|
level2 = os.path.join(level1, "level2")
|
|
level3 = os.path.join(level2, "level3")
|
|
self.addCleanup(os_helper.rmtree, level1)
|
|
|
|
os.mkdir(level1)
|
|
os.mkdir(level2)
|
|
os.mkdir(level3)
|
|
|
|
file1 = os.path.abspath(os.path.join(level1, "file1"))
|
|
create_file(file1)
|
|
|
|
orig_dir = os.getcwd()
|
|
try:
|
|
os.chdir(level2)
|
|
link = os.path.join(level2, "link")
|
|
os.symlink(os.path.relpath(file1), "link")
|
|
self.assertIn("link", os.listdir(os.getcwd()))
|
|
|
|
# Check os.stat calls from the same dir as the link
|
|
self.assertEqual(os.stat(file1), os.stat("link"))
|
|
|
|
# Check os.stat calls from a dir below the link
|
|
os.chdir(level1)
|
|
self.assertEqual(os.stat(file1),
|
|
os.stat(os.path.relpath(link)))
|
|
|
|
# Check os.stat calls from a dir above the link
|
|
os.chdir(level3)
|
|
self.assertEqual(os.stat(file1),
|
|
os.stat(os.path.relpath(link)))
|
|
finally:
|
|
os.chdir(orig_dir)
|
|
|
|
@unittest.skipUnless(os.path.lexists(r'C:\Users\All Users')
|
|
and os.path.exists(r'C:\ProgramData'),
|
|
'Test directories not found')
|
|
def test_29248(self):
|
|
# os.symlink() calls CreateSymbolicLink, which creates
|
|
# the reparse data buffer with the print name stored
|
|
# first, so the offset is always 0. CreateSymbolicLink
|
|
# stores the "PrintName" DOS path (e.g. "C:\") first,
|
|
# with an offset of 0, followed by the "SubstituteName"
|
|
# NT path (e.g. "\??\C:\"). The "All Users" link, on
|
|
# the other hand, seems to have been created manually
|
|
# with an inverted order.
|
|
target = os.readlink(r'C:\Users\All Users')
|
|
self.assertTrue(os.path.samefile(target, r'C:\ProgramData'))
|
|
|
|
def test_buffer_overflow(self):
|
|
# Older versions would have a buffer overflow when detecting
|
|
# whether a link source was a directory. This test ensures we
|
|
# no longer crash, but does not otherwise validate the behavior
|
|
segment = 'X' * 27
|
|
path = os.path.join(*[segment] * 10)
|
|
test_cases = [
|
|
# overflow with absolute src
|
|
('\\' + path, segment),
|
|
# overflow dest with relative src
|
|
(segment, path),
|
|
# overflow when joining src
|
|
(path[:180], path[:180]),
|
|
]
|
|
for src, dest in test_cases:
|
|
try:
|
|
os.symlink(src, dest)
|
|
except FileNotFoundError:
|
|
pass
|
|
else:
|
|
try:
|
|
os.remove(dest)
|
|
except OSError:
|
|
pass
|
|
# Also test with bytes, since that is a separate code path.
|
|
try:
|
|
os.symlink(os.fsencode(src), os.fsencode(dest))
|
|
except FileNotFoundError:
|
|
pass
|
|
else:
|
|
try:
|
|
os.remove(dest)
|
|
except OSError:
|
|
pass
|
|
|
|
def test_appexeclink(self):
|
|
root = os.path.expandvars(r'%LOCALAPPDATA%\Microsoft\WindowsApps')
|
|
if not os.path.isdir(root):
|
|
self.skipTest("test requires a WindowsApps directory")
|
|
|
|
aliases = [os.path.join(root, a)
|
|
for a in fnmatch.filter(os.listdir(root), '*.exe')]
|
|
|
|
for alias in aliases:
|
|
if support.verbose:
|
|
print()
|
|
print("Testing with", alias)
|
|
st = os.lstat(alias)
|
|
self.assertEqual(st, os.stat(alias))
|
|
self.assertFalse(stat.S_ISLNK(st.st_mode))
|
|
self.assertEqual(st.st_reparse_tag, stat.IO_REPARSE_TAG_APPEXECLINK)
|
|
self.assertTrue(os.path.isfile(alias))
|
|
# testing the first one we see is sufficient
|
|
break
|
|
else:
|
|
self.skipTest("test requires an app execution alias")
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
class Win32JunctionTests(unittest.TestCase):
|
|
junction = 'junctiontest'
|
|
junction_target = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
def setUp(self):
|
|
assert os.path.exists(self.junction_target)
|
|
assert not os.path.lexists(self.junction)
|
|
|
|
def tearDown(self):
|
|
if os.path.lexists(self.junction):
|
|
os.unlink(self.junction)
|
|
|
|
def test_create_junction(self):
|
|
_winapi.CreateJunction(self.junction_target, self.junction)
|
|
self.assertTrue(os.path.lexists(self.junction))
|
|
self.assertTrue(os.path.exists(self.junction))
|
|
self.assertTrue(os.path.isdir(self.junction))
|
|
self.assertNotEqual(os.stat(self.junction), os.lstat(self.junction))
|
|
self.assertEqual(os.stat(self.junction), os.stat(self.junction_target))
|
|
|
|
# bpo-37834: Junctions are not recognized as links.
|
|
self.assertFalse(os.path.islink(self.junction))
|
|
self.assertEqual(os.path.normcase("\\\\?\\" + self.junction_target),
|
|
os.path.normcase(os.readlink(self.junction)))
|
|
|
|
def test_unlink_removes_junction(self):
|
|
_winapi.CreateJunction(self.junction_target, self.junction)
|
|
self.assertTrue(os.path.exists(self.junction))
|
|
self.assertTrue(os.path.lexists(self.junction))
|
|
|
|
os.unlink(self.junction)
|
|
self.assertFalse(os.path.exists(self.junction))
|
|
|
|
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
|
class Win32NtTests(unittest.TestCase):
|
|
def test_getfinalpathname_handles(self):
|
|
nt = import_helper.import_module('nt')
|
|
ctypes = import_helper.import_module('ctypes')
|
|
import ctypes.wintypes
|
|
|
|
kernel = ctypes.WinDLL('Kernel32.dll', use_last_error=True)
|
|
kernel.GetCurrentProcess.restype = ctypes.wintypes.HANDLE
|
|
|
|
kernel.GetProcessHandleCount.restype = ctypes.wintypes.BOOL
|
|
kernel.GetProcessHandleCount.argtypes = (ctypes.wintypes.HANDLE,
|
|
ctypes.wintypes.LPDWORD)
|
|
|
|
# This is a pseudo-handle that doesn't need to be closed
|
|
hproc = kernel.GetCurrentProcess()
|
|
|
|
handle_count = ctypes.wintypes.DWORD()
|
|
ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count))
|
|
self.assertEqual(1, ok)
|
|
|
|
before_count = handle_count.value
|
|
|
|
# The first two test the error path, __file__ tests the success path
|
|
filenames = [
|
|
r'\\?\C:',
|
|
r'\\?\NUL',
|
|
r'\\?\CONIN',
|
|
__file__,
|
|
]
|
|
|
|
for _ in range(10):
|
|
for name in filenames:
|
|
try:
|
|
nt._getfinalpathname(name)
|
|
except Exception:
|
|
# Failure is expected
|
|
pass
|
|
try:
|
|
os.stat(name)
|
|
except Exception:
|
|
pass
|
|
|
|
ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count))
|
|
self.assertEqual(1, ok)
|
|
|
|
handle_delta = handle_count.value - before_count
|
|
|
|
self.assertEqual(0, handle_delta)
|
|
|
|
@support.requires_subprocess()
|
|
def test_stat_unlink_race(self):
|
|
# bpo-46785: the implementation of os.stat() falls back to reading
|
|
# the parent directory if CreateFileW() fails with a permission
|
|
# error. If reading the parent directory fails because the file or
|
|
# directory are subsequently unlinked, or because the volume or
|
|
# share are no longer available, then the original permission error
|
|
# should not be restored.
|
|
filename = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, filename)
|
|
deadline = time.time() + 5
|
|
command = textwrap.dedent("""\
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
filename = sys.argv[1]
|
|
deadline = float(sys.argv[2])
|
|
|
|
while time.time() < deadline:
|
|
try:
|
|
with open(filename, "w") as f:
|
|
pass
|
|
except OSError:
|
|
pass
|
|
try:
|
|
os.remove(filename)
|
|
except OSError:
|
|
pass
|
|
""")
|
|
|
|
with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
|
|
while time.time() < deadline:
|
|
try:
|
|
os.stat(filename)
|
|
except FileNotFoundError as e:
|
|
assert e.winerror == 2 # ERROR_FILE_NOT_FOUND
|
|
try:
|
|
proc.wait(1)
|
|
except subprocess.TimeoutExpired:
|
|
proc.terminate()
|
|
|
|
@support.requires_subprocess()
|
|
def test_stat_inaccessible_file(self):
|
|
filename = os_helper.TESTFN
|
|
ICACLS = os.path.expandvars(r"%SystemRoot%\System32\icacls.exe")
|
|
|
|
with open(filename, "wb") as f:
|
|
f.write(b'Test data')
|
|
|
|
stat1 = os.stat(filename)
|
|
|
|
try:
|
|
# Remove all permissions from the file
|
|
subprocess.check_output([ICACLS, filename, "/inheritance:r"],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as ex:
|
|
if support.verbose:
|
|
print(ICACLS, filename, "/inheritance:r", "failed.")
|
|
print(ex.stdout.decode("oem", "replace").rstrip())
|
|
try:
|
|
os.unlink(filename)
|
|
except OSError:
|
|
pass
|
|
self.skipTest("Unable to create inaccessible file")
|
|
|
|
def cleanup():
|
|
# Give delete permission. We are the file owner, so we can do this
|
|
# even though we removed all permissions earlier.
|
|
subprocess.check_output([ICACLS, filename, "/grant", "Everyone:(D)"],
|
|
stderr=subprocess.STDOUT)
|
|
os.unlink(filename)
|
|
|
|
self.addCleanup(cleanup)
|
|
|
|
if support.verbose:
|
|
print("File:", filename)
|
|
print("stat with access:", stat1)
|
|
|
|
# First test - we shouldn't raise here, because we still have access to
|
|
# the directory and can extract enough information from its metadata.
|
|
stat2 = os.stat(filename)
|
|
|
|
if support.verbose:
|
|
print(" without access:", stat2)
|
|
|
|
# We may not get st_dev/st_ino, so ensure those are 0 or match
|
|
self.assertIn(stat2.st_dev, (0, stat1.st_dev))
|
|
self.assertIn(stat2.st_ino, (0, stat1.st_ino))
|
|
|
|
# st_mode and st_size should match (for a normal file, at least)
|
|
self.assertEqual(stat1.st_mode, stat2.st_mode)
|
|
self.assertEqual(stat1.st_size, stat2.st_size)
|
|
|
|
# st_ctime and st_mtime should be the same
|
|
self.assertEqual(stat1.st_ctime, stat2.st_ctime)
|
|
self.assertEqual(stat1.st_mtime, stat2.st_mtime)
|
|
|
|
# st_atime should be the same or later
|
|
self.assertGreaterEqual(stat1.st_atime, stat2.st_atime)
|
|
|
|
|
|
@os_helper.skip_unless_symlink
|
|
class NonLocalSymlinkTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
r"""
|
|
Create this structure:
|
|
|
|
base
|
|
\___ some_dir
|
|
"""
|
|
os.makedirs('base/some_dir')
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree('base')
|
|
|
|
def test_directory_link_nonlocal(self):
|
|
"""
|
|
The symlink target should resolve relative to the link, not relative
|
|
to the current directory.
|
|
|
|
Then, link base/some_link -> base/some_dir and ensure that some_link
|
|
is resolved as a directory.
|
|
|
|
In issue13772, it was discovered that directory detection failed if
|
|
the symlink target was not specified relative to the current
|
|
directory, which was a defect in the implementation.
|
|
"""
|
|
src = os.path.join('base', 'some_link')
|
|
os.symlink('some_dir', src)
|
|
assert os.path.isdir(src)
|
|
|
|
|
|
class FSEncodingTests(unittest.TestCase):
|
|
def test_nop(self):
|
|
self.assertEqual(os.fsencode(b'abc\xff'), b'abc\xff')
|
|
self.assertEqual(os.fsdecode('abc\u0141'), 'abc\u0141')
|
|
|
|
def test_identity(self):
|
|
# assert fsdecode(fsencode(x)) == x
|
|
for fn in ('unicode\u0141', 'latin\xe9', 'ascii'):
|
|
try:
|
|
bytesfn = os.fsencode(fn)
|
|
except UnicodeEncodeError:
|
|
continue
|
|
self.assertEqual(os.fsdecode(bytesfn), fn)
|
|
|
|
|
|
|
|
class DeviceEncodingTests(unittest.TestCase):
|
|
|
|
def test_bad_fd(self):
|
|
# Return None when an fd doesn't actually exist.
|
|
self.assertIsNone(os.device_encoding(123456))
|
|
|
|
@unittest.skipUnless(os.isatty(0) and not win32_is_iot() and (sys.platform.startswith('win') or
|
|
(hasattr(locale, 'nl_langinfo') and hasattr(locale, 'CODESET'))),
|
|
'test requires a tty and either Windows or nl_langinfo(CODESET)')
|
|
@unittest.skipIf(
|
|
support.is_emscripten, "Cannot get encoding of stdin on Emscripten"
|
|
)
|
|
def test_device_encoding(self):
|
|
encoding = os.device_encoding(0)
|
|
self.assertIsNotNone(encoding)
|
|
self.assertTrue(codecs.lookup(encoding))
|
|
|
|
|
|
@support.requires_subprocess()
|
|
class PidTests(unittest.TestCase):
|
|
@unittest.skipUnless(hasattr(os, 'getppid'), "test needs os.getppid")
|
|
def test_getppid(self):
|
|
p = subprocess.Popen([sys._base_executable, '-c',
|
|
'import os; print(os.getppid())'],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdout, error = p.communicate()
|
|
# We are the parent of our subprocess
|
|
self.assertEqual(error, b'')
|
|
self.assertEqual(int(stdout), os.getpid())
|
|
|
|
def check_waitpid(self, code, exitcode, callback=None):
|
|
if sys.platform == 'win32':
|
|
# On Windows, os.spawnv() simply joins arguments with spaces:
|
|
# arguments need to be quoted
|
|
args = [f'"{sys.executable}"', '-c', f'"{code}"']
|
|
else:
|
|
args = [sys.executable, '-c', code]
|
|
pid = os.spawnv(os.P_NOWAIT, sys.executable, args)
|
|
|
|
if callback is not None:
|
|
callback(pid)
|
|
|
|
# don't use support.wait_process() to test directly os.waitpid()
|
|
# and os.waitstatus_to_exitcode()
|
|
pid2, status = os.waitpid(pid, 0)
|
|
self.assertEqual(os.waitstatus_to_exitcode(status), exitcode)
|
|
self.assertEqual(pid2, pid)
|
|
|
|
def test_waitpid(self):
|
|
self.check_waitpid(code='pass', exitcode=0)
|
|
|
|
def test_waitstatus_to_exitcode(self):
|
|
exitcode = 23
|
|
code = f'import sys; sys.exit({exitcode})'
|
|
self.check_waitpid(code, exitcode=exitcode)
|
|
|
|
with self.assertRaises(TypeError):
|
|
os.waitstatus_to_exitcode(0.0)
|
|
|
|
@unittest.skipUnless(sys.platform == 'win32', 'win32-specific test')
|
|
def test_waitpid_windows(self):
|
|
# bpo-40138: test os.waitpid() and os.waitstatus_to_exitcode()
|
|
# with exit code larger than INT_MAX.
|
|
STATUS_CONTROL_C_EXIT = 0xC000013A
|
|
code = f'import _winapi; _winapi.ExitProcess({STATUS_CONTROL_C_EXIT})'
|
|
self.check_waitpid(code, exitcode=STATUS_CONTROL_C_EXIT)
|
|
|
|
@unittest.skipUnless(sys.platform == 'win32', 'win32-specific test')
|
|
def test_waitstatus_to_exitcode_windows(self):
|
|
max_exitcode = 2 ** 32 - 1
|
|
for exitcode in (0, 1, 5, max_exitcode):
|
|
self.assertEqual(os.waitstatus_to_exitcode(exitcode << 8),
|
|
exitcode)
|
|
|
|
# invalid values
|
|
with self.assertRaises(ValueError):
|
|
os.waitstatus_to_exitcode((max_exitcode + 1) << 8)
|
|
with self.assertRaises(OverflowError):
|
|
os.waitstatus_to_exitcode(-1)
|
|
|
|
# Skip the test on Windows
|
|
@unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'need signal.SIGKILL')
|
|
def test_waitstatus_to_exitcode_kill(self):
|
|
code = f'import time; time.sleep({support.LONG_TIMEOUT})'
|
|
signum = signal.SIGKILL
|
|
|
|
def kill_process(pid):
|
|
os.kill(pid, signum)
|
|
|
|
self.check_waitpid(code, exitcode=-signum, callback=kill_process)
|
|
|
|
|
|
@support.requires_subprocess()
|
|
class SpawnTests(unittest.TestCase):
|
|
@staticmethod
|
|
def quote_args(args):
|
|
# On Windows, os.spawn* simply joins arguments with spaces:
|
|
# arguments need to be quoted
|
|
if os.name != 'nt':
|
|
return args
|
|
return [f'"{arg}"' if " " in arg.strip() else arg for arg in args]
|
|
|
|
def create_args(self, *, with_env=False, use_bytes=False):
|
|
self.exitcode = 17
|
|
|
|
filename = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, filename)
|
|
|
|
if not with_env:
|
|
code = 'import sys; sys.exit(%s)' % self.exitcode
|
|
else:
|
|
self.env = dict(os.environ)
|
|
# create an unique key
|
|
self.key = str(uuid.uuid4())
|
|
self.env[self.key] = self.key
|
|
# read the variable from os.environ to check that it exists
|
|
code = ('import sys, os; magic = os.environ[%r]; sys.exit(%s)'
|
|
% (self.key, self.exitcode))
|
|
|
|
with open(filename, "w", encoding="utf-8") as fp:
|
|
fp.write(code)
|
|
|
|
program = sys.executable
|
|
args = self.quote_args([program, filename])
|
|
if use_bytes:
|
|
program = os.fsencode(program)
|
|
args = [os.fsencode(a) for a in args]
|
|
self.env = {os.fsencode(k): os.fsencode(v)
|
|
for k, v in self.env.items()}
|
|
|
|
return program, args
|
|
|
|
@requires_os_func('spawnl')
|
|
def test_spawnl(self):
|
|
program, args = self.create_args()
|
|
exitcode = os.spawnl(os.P_WAIT, program, *args)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnle')
|
|
def test_spawnle(self):
|
|
program, args = self.create_args(with_env=True)
|
|
exitcode = os.spawnle(os.P_WAIT, program, *args, self.env)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnlp')
|
|
def test_spawnlp(self):
|
|
program, args = self.create_args()
|
|
exitcode = os.spawnlp(os.P_WAIT, program, *args)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnlpe')
|
|
def test_spawnlpe(self):
|
|
program, args = self.create_args(with_env=True)
|
|
exitcode = os.spawnlpe(os.P_WAIT, program, *args, self.env)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnv')
|
|
def test_spawnv(self):
|
|
program, args = self.create_args()
|
|
exitcode = os.spawnv(os.P_WAIT, program, args)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
# Test for PyUnicode_FSConverter()
|
|
exitcode = os.spawnv(os.P_WAIT, FakePath(program), args)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnve')
|
|
def test_spawnve(self):
|
|
program, args = self.create_args(with_env=True)
|
|
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnvp')
|
|
def test_spawnvp(self):
|
|
program, args = self.create_args()
|
|
exitcode = os.spawnvp(os.P_WAIT, program, args)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnvpe')
|
|
def test_spawnvpe(self):
|
|
program, args = self.create_args(with_env=True)
|
|
exitcode = os.spawnvpe(os.P_WAIT, program, args, self.env)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnv')
|
|
def test_nowait(self):
|
|
program, args = self.create_args()
|
|
pid = os.spawnv(os.P_NOWAIT, program, args)
|
|
support.wait_process(pid, exitcode=self.exitcode)
|
|
|
|
@requires_os_func('spawnve')
|
|
def test_spawnve_bytes(self):
|
|
# Test bytes handling in parse_arglist and parse_envlist (#28114)
|
|
program, args = self.create_args(with_env=True, use_bytes=True)
|
|
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
|
|
self.assertEqual(exitcode, self.exitcode)
|
|
|
|
@requires_os_func('spawnl')
|
|
def test_spawnl_noargs(self):
|
|
program, __ = self.create_args()
|
|
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program)
|
|
self.assertRaises(ValueError, os.spawnl, os.P_NOWAIT, program, '')
|
|
|
|
@requires_os_func('spawnle')
|
|
def test_spawnle_noargs(self):
|
|
program, __ = self.create_args()
|
|
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, {})
|
|
self.assertRaises(ValueError, os.spawnle, os.P_NOWAIT, program, '', {})
|
|
|
|
@requires_os_func('spawnv')
|
|
def test_spawnv_noargs(self):
|
|
program, __ = self.create_args()
|
|
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ())
|
|
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [])
|
|
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, ('',))
|
|
self.assertRaises(ValueError, os.spawnv, os.P_NOWAIT, program, [''])
|
|
|
|
@requires_os_func('spawnve')
|
|
def test_spawnve_noargs(self):
|
|
program, __ = self.create_args()
|
|
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, (), {})
|
|
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [], {})
|
|
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, ('',), {})
|
|
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [''], {})
|
|
|
|
def _test_invalid_env(self, spawn):
|
|
program = sys.executable
|
|
args = self.quote_args([program, '-c', 'pass'])
|
|
|
|
# null character in the environment variable name
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT\0VEGETABLE"] = "cabbage"
|
|
try:
|
|
exitcode = spawn(os.P_WAIT, program, args, newenv)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.assertEqual(exitcode, 127)
|
|
|
|
# null character in the environment variable value
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
|
|
try:
|
|
exitcode = spawn(os.P_WAIT, program, args, newenv)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.assertEqual(exitcode, 127)
|
|
|
|
# equal character in the environment variable name
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT=ORANGE"] = "lemon"
|
|
try:
|
|
exitcode = spawn(os.P_WAIT, program, args, newenv)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.assertEqual(exitcode, 127)
|
|
|
|
# equal character in the environment variable value
|
|
filename = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, filename)
|
|
with open(filename, "w", encoding="utf-8") as fp:
|
|
fp.write('import sys, os\n'
|
|
'if os.getenv("FRUIT") != "orange=lemon":\n'
|
|
' raise AssertionError')
|
|
|
|
args = self.quote_args([program, filename])
|
|
newenv = os.environ.copy()
|
|
newenv["FRUIT"] = "orange=lemon"
|
|
exitcode = spawn(os.P_WAIT, program, args, newenv)
|
|
self.assertEqual(exitcode, 0)
|
|
|
|
@requires_os_func('spawnve')
|
|
def test_spawnve_invalid_env(self):
|
|
self._test_invalid_env(os.spawnve)
|
|
|
|
@requires_os_func('spawnvpe')
|
|
def test_spawnvpe_invalid_env(self):
|
|
self._test_invalid_env(os.spawnvpe)
|
|
|
|
|
|
# The introduction of this TestCase caused at least two different errors on
|
|
# *nix buildbots. Temporarily skip this to let the buildbots move along.
|
|
@unittest.skip("Skip due to platform/environment differences on *NIX buildbots")
|
|
@unittest.skipUnless(hasattr(os, 'getlogin'), "test needs os.getlogin")
|
|
class LoginTests(unittest.TestCase):
|
|
def test_getlogin(self):
|
|
user_name = os.getlogin()
|
|
self.assertNotEqual(len(user_name), 0)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'getpriority') and hasattr(os, 'setpriority'),
|
|
"needs os.getpriority and os.setpriority")
|
|
class ProgramPriorityTests(unittest.TestCase):
|
|
"""Tests for os.getpriority() and os.setpriority()."""
|
|
|
|
def test_set_get_priority(self):
|
|
base = os.getpriority(os.PRIO_PROCESS, os.getpid())
|
|
code = f"""if 1:
|
|
import os
|
|
os.setpriority(os.PRIO_PROCESS, os.getpid(), {base} + 1)
|
|
print(os.getpriority(os.PRIO_PROCESS, os.getpid()))
|
|
"""
|
|
|
|
# Subprocess inherits the current process' priority.
|
|
_, out, _ = assert_python_ok("-c", code)
|
|
new_prio = int(out)
|
|
# nice value cap is 19 for linux and 20 for FreeBSD
|
|
if base >= 19 and new_prio <= base:
|
|
raise unittest.SkipTest("unable to reliably test setpriority "
|
|
"at current nice level of %s" % base)
|
|
else:
|
|
self.assertEqual(new_prio, base + 1)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()")
|
|
class TestSendfile(unittest.IsolatedAsyncioTestCase):
|
|
|
|
DATA = b"12345abcde" * 16 * 1024 # 160 KiB
|
|
SUPPORT_HEADERS_TRAILERS = (
|
|
not sys.platform.startswith(("linux", "android", "solaris", "sunos")))
|
|
requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS,
|
|
'requires headers and trailers support')
|
|
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
|
|
'test is only meaningful on 32-bit builds')
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
create_file(os_helper.TESTFN, cls.DATA)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
os_helper.unlink(os_helper.TESTFN)
|
|
|
|
@staticmethod
|
|
async def chunks(reader):
|
|
while not reader.at_eof():
|
|
yield await reader.read()
|
|
|
|
async def handle_new_client(self, reader, writer):
|
|
self.server_buffer = b''.join([x async for x in self.chunks(reader)])
|
|
writer.close()
|
|
self.server.close() # The test server processes a single client only
|
|
|
|
async def asyncSetUp(self):
|
|
self.server_buffer = b''
|
|
self.server = await asyncio.start_server(self.handle_new_client,
|
|
socket_helper.HOSTv4)
|
|
server_name = self.server.sockets[0].getsockname()
|
|
self.client = socket.socket()
|
|
self.client.setblocking(False)
|
|
await asyncio.get_running_loop().sock_connect(self.client, server_name)
|
|
self.sockno = self.client.fileno()
|
|
self.file = open(os_helper.TESTFN, 'rb')
|
|
self.fileno = self.file.fileno()
|
|
|
|
async def asyncTearDown(self):
|
|
self.file.close()
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
|
|
# Use the test subject instead of asyncio.loop.sendfile
|
|
@staticmethod
|
|
async def async_sendfile(*args, **kwargs):
|
|
return await asyncio.to_thread(os.sendfile, *args, **kwargs)
|
|
|
|
@staticmethod
|
|
async def sendfile_wrapper(*args, **kwargs):
|
|
"""A higher level wrapper representing how an application is
|
|
supposed to use sendfile().
|
|
"""
|
|
while True:
|
|
try:
|
|
return await TestSendfile.async_sendfile(*args, **kwargs)
|
|
except OSError as err:
|
|
if err.errno == errno.ECONNRESET:
|
|
# disconnected
|
|
raise
|
|
elif err.errno in (errno.EAGAIN, errno.EBUSY):
|
|
# we have to retry send data
|
|
continue
|
|
else:
|
|
raise
|
|
|
|
async def test_send_whole_file(self):
|
|
# normal send
|
|
total_sent = 0
|
|
offset = 0
|
|
nbytes = 4096
|
|
while total_sent < len(self.DATA):
|
|
sent = await self.sendfile_wrapper(self.sockno, self.fileno,
|
|
offset, nbytes)
|
|
if sent == 0:
|
|
break
|
|
offset += sent
|
|
total_sent += sent
|
|
self.assertTrue(sent <= nbytes)
|
|
self.assertEqual(offset, total_sent)
|
|
|
|
self.assertEqual(total_sent, len(self.DATA))
|
|
self.client.shutdown(socket.SHUT_RDWR)
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
self.assertEqual(len(self.server_buffer), len(self.DATA))
|
|
self.assertEqual(self.server_buffer, self.DATA)
|
|
|
|
async def test_send_at_certain_offset(self):
|
|
# start sending a file at a certain offset
|
|
total_sent = 0
|
|
offset = len(self.DATA) // 2
|
|
must_send = len(self.DATA) - offset
|
|
nbytes = 4096
|
|
while total_sent < must_send:
|
|
sent = await self.sendfile_wrapper(self.sockno, self.fileno,
|
|
offset, nbytes)
|
|
if sent == 0:
|
|
break
|
|
offset += sent
|
|
total_sent += sent
|
|
self.assertTrue(sent <= nbytes)
|
|
|
|
self.client.shutdown(socket.SHUT_RDWR)
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
expected = self.DATA[len(self.DATA) // 2:]
|
|
self.assertEqual(total_sent, len(expected))
|
|
self.assertEqual(len(self.server_buffer), len(expected))
|
|
self.assertEqual(self.server_buffer, expected)
|
|
|
|
async def test_offset_overflow(self):
|
|
# specify an offset > file size
|
|
offset = len(self.DATA) + 4096
|
|
try:
|
|
sent = await self.async_sendfile(self.sockno, self.fileno,
|
|
offset, 4096)
|
|
except OSError as e:
|
|
# Solaris can raise EINVAL if offset >= file length, ignore.
|
|
if e.errno != errno.EINVAL:
|
|
raise
|
|
else:
|
|
self.assertEqual(sent, 0)
|
|
self.client.shutdown(socket.SHUT_RDWR)
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
self.assertEqual(self.server_buffer, b'')
|
|
|
|
async def test_invalid_offset(self):
|
|
with self.assertRaises(OSError) as cm:
|
|
await self.async_sendfile(self.sockno, self.fileno, -1, 4096)
|
|
self.assertEqual(cm.exception.errno, errno.EINVAL)
|
|
|
|
async def test_keywords(self):
|
|
# Keyword arguments should be supported
|
|
await self.async_sendfile(out_fd=self.sockno, in_fd=self.fileno,
|
|
offset=0, count=4096)
|
|
if self.SUPPORT_HEADERS_TRAILERS:
|
|
await self.async_sendfile(out_fd=self.sockno, in_fd=self.fileno,
|
|
offset=0, count=4096,
|
|
headers=(), trailers=(), flags=0)
|
|
|
|
# --- headers / trailers tests
|
|
|
|
@requires_headers_trailers
|
|
async def test_headers(self):
|
|
total_sent = 0
|
|
expected_data = b"x" * 512 + b"y" * 256 + self.DATA[:-1]
|
|
sent = await self.async_sendfile(self.sockno, self.fileno, 0, 4096,
|
|
headers=[b"x" * 512, b"y" * 256])
|
|
self.assertLessEqual(sent, 512 + 256 + 4096)
|
|
total_sent += sent
|
|
offset = 4096
|
|
while total_sent < len(expected_data):
|
|
nbytes = min(len(expected_data) - total_sent, 4096)
|
|
sent = await self.sendfile_wrapper(self.sockno, self.fileno,
|
|
offset, nbytes)
|
|
if sent == 0:
|
|
break
|
|
self.assertLessEqual(sent, nbytes)
|
|
total_sent += sent
|
|
offset += sent
|
|
|
|
self.assertEqual(total_sent, len(expected_data))
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
self.assertEqual(hash(self.server_buffer), hash(expected_data))
|
|
|
|
@requires_headers_trailers
|
|
async def test_trailers(self):
|
|
TESTFN2 = os_helper.TESTFN + "2"
|
|
file_data = b"abcdef"
|
|
|
|
self.addCleanup(os_helper.unlink, TESTFN2)
|
|
create_file(TESTFN2, file_data)
|
|
|
|
with open(TESTFN2, 'rb') as f:
|
|
await self.async_sendfile(self.sockno, f.fileno(), 0, 5,
|
|
trailers=[b"123456", b"789"])
|
|
self.client.close()
|
|
await self.server.wait_closed()
|
|
self.assertEqual(self.server_buffer, b"abcde123456789")
|
|
|
|
@requires_headers_trailers
|
|
@requires_32b
|
|
async def test_headers_overflow_32bits(self):
|
|
self.server.handler_instance.accumulate = False
|
|
with self.assertRaises(OSError) as cm:
|
|
await self.async_sendfile(self.sockno, self.fileno, 0, 0,
|
|
headers=[b"x" * 2**16] * 2**15)
|
|
self.assertEqual(cm.exception.errno, errno.EINVAL)
|
|
|
|
@requires_headers_trailers
|
|
@requires_32b
|
|
async def test_trailers_overflow_32bits(self):
|
|
self.server.handler_instance.accumulate = False
|
|
with self.assertRaises(OSError) as cm:
|
|
await self.async_sendfile(self.sockno, self.fileno, 0, 0,
|
|
trailers=[b"x" * 2**16] * 2**15)
|
|
self.assertEqual(cm.exception.errno, errno.EINVAL)
|
|
|
|
@requires_headers_trailers
|
|
@unittest.skipUnless(hasattr(os, 'SF_NODISKIO'),
|
|
'test needs os.SF_NODISKIO')
|
|
async def test_flags(self):
|
|
try:
|
|
await self.async_sendfile(self.sockno, self.fileno, 0, 4096,
|
|
flags=os.SF_NODISKIO)
|
|
except OSError as err:
|
|
if err.errno not in (errno.EBUSY, errno.EAGAIN):
|
|
raise
|
|
|
|
|
|
def supports_extended_attributes():
|
|
if not hasattr(os, "setxattr"):
|
|
return False
|
|
|
|
try:
|
|
with open(os_helper.TESTFN, "xb", 0) as fp:
|
|
try:
|
|
os.setxattr(fp.fileno(), b"user.test", b"")
|
|
except OSError:
|
|
return False
|
|
finally:
|
|
os_helper.unlink(os_helper.TESTFN)
|
|
|
|
return True
|
|
|
|
|
|
@unittest.skipUnless(supports_extended_attributes(),
|
|
"no non-broken extended attribute support")
|
|
# Kernels < 2.6.39 don't respect setxattr flags.
|
|
@support.requires_linux_version(2, 6, 39)
|
|
class ExtendedAttributeTests(unittest.TestCase):
|
|
|
|
def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwargs):
|
|
fn = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, fn)
|
|
create_file(fn)
|
|
|
|
with self.assertRaises(OSError) as cm:
|
|
getxattr(fn, s("user.test"), **kwargs)
|
|
self.assertEqual(cm.exception.errno, errno.ENODATA)
|
|
|
|
init_xattr = listxattr(fn)
|
|
self.assertIsInstance(init_xattr, list)
|
|
|
|
setxattr(fn, s("user.test"), b"", **kwargs)
|
|
xattr = set(init_xattr)
|
|
xattr.add("user.test")
|
|
self.assertEqual(set(listxattr(fn)), xattr)
|
|
self.assertEqual(getxattr(fn, b"user.test", **kwargs), b"")
|
|
setxattr(fn, s("user.test"), b"hello", os.XATTR_REPLACE, **kwargs)
|
|
self.assertEqual(getxattr(fn, b"user.test", **kwargs), b"hello")
|
|
|
|
with self.assertRaises(OSError) as cm:
|
|
setxattr(fn, s("user.test"), b"bye", os.XATTR_CREATE, **kwargs)
|
|
self.assertEqual(cm.exception.errno, errno.EEXIST)
|
|
|
|
with self.assertRaises(OSError) as cm:
|
|
setxattr(fn, s("user.test2"), b"bye", os.XATTR_REPLACE, **kwargs)
|
|
self.assertEqual(cm.exception.errno, errno.ENODATA)
|
|
|
|
setxattr(fn, s("user.test2"), b"foo", os.XATTR_CREATE, **kwargs)
|
|
xattr.add("user.test2")
|
|
self.assertEqual(set(listxattr(fn)), xattr)
|
|
removexattr(fn, s("user.test"), **kwargs)
|
|
|
|
with self.assertRaises(OSError) as cm:
|
|
getxattr(fn, s("user.test"), **kwargs)
|
|
self.assertEqual(cm.exception.errno, errno.ENODATA)
|
|
|
|
xattr.remove("user.test")
|
|
self.assertEqual(set(listxattr(fn)), xattr)
|
|
self.assertEqual(getxattr(fn, s("user.test2"), **kwargs), b"foo")
|
|
setxattr(fn, s("user.test"), b"a"*1024, **kwargs)
|
|
self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*1024)
|
|
removexattr(fn, s("user.test"), **kwargs)
|
|
many = sorted("user.test{}".format(i) for i in range(100))
|
|
for thing in many:
|
|
setxattr(fn, thing, b"x", **kwargs)
|
|
self.assertEqual(set(listxattr(fn)), set(init_xattr) | set(many))
|
|
|
|
def _check_xattrs(self, *args, **kwargs):
|
|
self._check_xattrs_str(str, *args, **kwargs)
|
|
os_helper.unlink(os_helper.TESTFN)
|
|
|
|
self._check_xattrs_str(os.fsencode, *args, **kwargs)
|
|
os_helper.unlink(os_helper.TESTFN)
|
|
|
|
def test_simple(self):
|
|
self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
|
|
os.listxattr)
|
|
|
|
def test_lpath(self):
|
|
self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
|
|
os.listxattr, follow_symlinks=False)
|
|
|
|
def test_fds(self):
|
|
def getxattr(path, *args):
|
|
with open(path, "rb") as fp:
|
|
return os.getxattr(fp.fileno(), *args)
|
|
def setxattr(path, *args):
|
|
with open(path, "wb", 0) as fp:
|
|
os.setxattr(fp.fileno(), *args)
|
|
def removexattr(path, *args):
|
|
with open(path, "wb", 0) as fp:
|
|
os.removexattr(fp.fileno(), *args)
|
|
def listxattr(path, *args):
|
|
with open(path, "rb") as fp:
|
|
return os.listxattr(fp.fileno(), *args)
|
|
self._check_xattrs(getxattr, setxattr, removexattr, listxattr)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'get_terminal_size'), "requires os.get_terminal_size")
|
|
class TermsizeTests(unittest.TestCase):
|
|
def test_does_not_crash(self):
|
|
"""Check if get_terminal_size() returns a meaningful value.
|
|
|
|
There's no easy portable way to actually check the size of the
|
|
terminal, so let's check if it returns something sensible instead.
|
|
"""
|
|
try:
|
|
size = os.get_terminal_size()
|
|
except OSError as e:
|
|
if sys.platform == "win32" or e.errno in (errno.EINVAL, errno.ENOTTY):
|
|
# Under win32 a generic OSError can be thrown if the
|
|
# handle cannot be retrieved
|
|
self.skipTest("failed to query terminal size")
|
|
raise
|
|
|
|
self.assertGreaterEqual(size.columns, 0)
|
|
self.assertGreaterEqual(size.lines, 0)
|
|
|
|
@support.requires_subprocess()
|
|
def test_stty_match(self):
|
|
"""Check if stty returns the same results
|
|
|
|
stty actually tests stdin, so get_terminal_size is invoked on
|
|
stdin explicitly. If stty succeeded, then get_terminal_size()
|
|
should work too.
|
|
"""
|
|
try:
|
|
size = (
|
|
subprocess.check_output(
|
|
["stty", "size"], stderr=subprocess.DEVNULL, text=True
|
|
).split()
|
|
)
|
|
except (FileNotFoundError, subprocess.CalledProcessError,
|
|
PermissionError):
|
|
self.skipTest("stty invocation failed")
|
|
expected = (int(size[1]), int(size[0])) # reversed order
|
|
|
|
try:
|
|
actual = os.get_terminal_size(sys.__stdin__.fileno())
|
|
except OSError as e:
|
|
if sys.platform == "win32" or e.errno in (errno.EINVAL, errno.ENOTTY):
|
|
# Under win32 a generic OSError can be thrown if the
|
|
# handle cannot be retrieved
|
|
self.skipTest("failed to query terminal size")
|
|
raise
|
|
self.assertEqual(expected, actual)
|
|
|
|
@unittest.skipUnless(sys.platform == 'win32', 'Windows specific test')
|
|
def test_windows_fd(self):
|
|
"""Check if get_terminal_size() returns a meaningful value in Windows"""
|
|
try:
|
|
conout = open('conout$', 'w')
|
|
except OSError:
|
|
self.skipTest('failed to open conout$')
|
|
with conout:
|
|
size = os.get_terminal_size(conout.fileno())
|
|
|
|
self.assertGreaterEqual(size.columns, 0)
|
|
self.assertGreaterEqual(size.lines, 0)
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'memfd_create'), 'requires os.memfd_create')
|
|
@support.requires_linux_version(3, 17)
|
|
class MemfdCreateTests(unittest.TestCase):
|
|
def test_memfd_create(self):
|
|
fd = os.memfd_create("Hi", os.MFD_CLOEXEC)
|
|
self.assertNotEqual(fd, -1)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertFalse(os.get_inheritable(fd))
|
|
with open(fd, "wb", closefd=False) as f:
|
|
f.write(b'memfd_create')
|
|
self.assertEqual(f.tell(), 12)
|
|
|
|
fd2 = os.memfd_create("Hi")
|
|
self.addCleanup(os.close, fd2)
|
|
self.assertFalse(os.get_inheritable(fd2))
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd')
|
|
@support.requires_linux_version(2, 6, 30)
|
|
class EventfdTests(unittest.TestCase):
|
|
def test_eventfd_initval(self):
|
|
def pack(value):
|
|
"""Pack as native uint64_t
|
|
"""
|
|
return struct.pack("@Q", value)
|
|
size = 8 # read/write 8 bytes
|
|
initval = 42
|
|
fd = os.eventfd(initval)
|
|
self.assertNotEqual(fd, -1)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertFalse(os.get_inheritable(fd))
|
|
|
|
# test with raw read/write
|
|
res = os.read(fd, size)
|
|
self.assertEqual(res, pack(initval))
|
|
|
|
os.write(fd, pack(23))
|
|
res = os.read(fd, size)
|
|
self.assertEqual(res, pack(23))
|
|
|
|
os.write(fd, pack(40))
|
|
os.write(fd, pack(2))
|
|
res = os.read(fd, size)
|
|
self.assertEqual(res, pack(42))
|
|
|
|
# test with eventfd_read/eventfd_write
|
|
os.eventfd_write(fd, 20)
|
|
os.eventfd_write(fd, 3)
|
|
res = os.eventfd_read(fd)
|
|
self.assertEqual(res, 23)
|
|
|
|
def test_eventfd_semaphore(self):
|
|
initval = 2
|
|
flags = os.EFD_CLOEXEC | os.EFD_SEMAPHORE | os.EFD_NONBLOCK
|
|
fd = os.eventfd(initval, flags)
|
|
self.assertNotEqual(fd, -1)
|
|
self.addCleanup(os.close, fd)
|
|
|
|
# semaphore starts has initval 2, two reads return '1'
|
|
res = os.eventfd_read(fd)
|
|
self.assertEqual(res, 1)
|
|
res = os.eventfd_read(fd)
|
|
self.assertEqual(res, 1)
|
|
# third read would block
|
|
with self.assertRaises(BlockingIOError):
|
|
os.eventfd_read(fd)
|
|
with self.assertRaises(BlockingIOError):
|
|
os.read(fd, 8)
|
|
|
|
# increase semaphore counter, read one
|
|
os.eventfd_write(fd, 1)
|
|
res = os.eventfd_read(fd)
|
|
self.assertEqual(res, 1)
|
|
# next read would block, too
|
|
with self.assertRaises(BlockingIOError):
|
|
os.eventfd_read(fd)
|
|
|
|
def test_eventfd_select(self):
|
|
flags = os.EFD_CLOEXEC | os.EFD_NONBLOCK
|
|
fd = os.eventfd(0, flags)
|
|
self.assertNotEqual(fd, -1)
|
|
self.addCleanup(os.close, fd)
|
|
|
|
# counter is zero, only writeable
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
|
|
self.assertEqual((rfd, wfd, xfd), ([], [fd], []))
|
|
|
|
# counter is non-zero, read and writeable
|
|
os.eventfd_write(fd, 23)
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
|
|
self.assertEqual((rfd, wfd, xfd), ([fd], [fd], []))
|
|
self.assertEqual(os.eventfd_read(fd), 23)
|
|
|
|
# counter at max, only readable
|
|
os.eventfd_write(fd, (2**64) - 2)
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
|
|
self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
|
|
os.eventfd_read(fd)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'timerfd_create'), 'requires os.timerfd_create')
|
|
@support.requires_linux_version(2, 6, 30)
|
|
class TimerfdTests(unittest.TestCase):
|
|
# Tolerate a difference of 1 ms
|
|
CLOCK_RES_NS = 1_000_000
|
|
CLOCK_RES = CLOCK_RES_NS * 1e-9
|
|
|
|
def timerfd_create(self, *args, **kwargs):
|
|
fd = os.timerfd_create(*args, **kwargs)
|
|
self.assertGreaterEqual(fd, 0)
|
|
self.assertFalse(os.get_inheritable(fd))
|
|
self.addCleanup(os.close, fd)
|
|
return fd
|
|
|
|
def read_count_signaled(self, fd):
|
|
# read 8 bytes
|
|
data = os.read(fd, 8)
|
|
return int.from_bytes(data, byteorder=sys.byteorder)
|
|
|
|
def test_timerfd_initval(self):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
initial_expiration = 0.25
|
|
interval = 0.125
|
|
|
|
# 1st call
|
|
next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
|
|
self.assertAlmostEqual(interval2, 0.0, places=3)
|
|
self.assertAlmostEqual(next_expiration, 0.0, places=3)
|
|
|
|
# 2nd call
|
|
next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
|
|
self.assertAlmostEqual(interval2, interval, places=3)
|
|
self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
|
|
|
|
# timerfd_gettime
|
|
next_expiration, interval2 = os.timerfd_gettime(fd)
|
|
self.assertAlmostEqual(interval2, interval, places=3)
|
|
self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
|
|
|
|
def test_timerfd_non_blocking(self):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
|
|
|
|
# 0.1 second later
|
|
initial_expiration = 0.1
|
|
os.timerfd_settime(fd, initial=initial_expiration, interval=0)
|
|
|
|
# read() raises OSError with errno is EAGAIN for non-blocking timer.
|
|
with self.assertRaises(OSError) as ctx:
|
|
self.read_count_signaled(fd)
|
|
self.assertEqual(ctx.exception.errno, errno.EAGAIN)
|
|
|
|
# Wait more than 0.1 seconds
|
|
time.sleep(initial_expiration + 0.1)
|
|
|
|
# confirm if timerfd is readable and read() returns 1 as bytes.
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
|
|
def test_timerfd_negative(self):
|
|
one_sec_in_nsec = 10**9
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
test_flags = [0, os.TFD_TIMER_ABSTIME]
|
|
if hasattr(os, 'TFD_TIMER_CANCEL_ON_SET'):
|
|
test_flags.append(os.TFD_TIMER_ABSTIME | os.TFD_TIMER_CANCEL_ON_SET)
|
|
|
|
# Any of 'initial' and 'interval' is negative value.
|
|
for initial, interval in ( (-1, 0), (1, -1), (-1, -1), (-0.1, 0), (1, -0.1), (-0.1, -0.1)):
|
|
for flags in test_flags:
|
|
with self.subTest(flags=flags, initial=initial, interval=interval):
|
|
with self.assertRaises(OSError) as context:
|
|
os.timerfd_settime(fd, flags=flags, initial=initial, interval=interval)
|
|
self.assertEqual(context.exception.errno, errno.EINVAL)
|
|
|
|
with self.assertRaises(OSError) as context:
|
|
initial_ns = int( one_sec_in_nsec * initial )
|
|
interval_ns = int( one_sec_in_nsec * interval )
|
|
os.timerfd_settime_ns(fd, flags=flags, initial=initial_ns, interval=interval_ns)
|
|
self.assertEqual(context.exception.errno, errno.EINVAL)
|
|
|
|
def test_timerfd_interval(self):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
# 1 second
|
|
initial_expiration = 1
|
|
# 0.5 second
|
|
interval = 0.5
|
|
|
|
os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
|
|
|
|
# timerfd_gettime
|
|
next_expiration, interval2 = os.timerfd_gettime(fd)
|
|
self.assertAlmostEqual(interval2, interval, places=3)
|
|
self.assertAlmostEqual(next_expiration, initial_expiration, places=3)
|
|
|
|
count = 3
|
|
t = time.perf_counter()
|
|
for _ in range(count):
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
t = time.perf_counter() - t
|
|
|
|
total_time = initial_expiration + interval * (count - 1)
|
|
self.assertGreater(t, total_time - self.CLOCK_RES)
|
|
|
|
# wait 3.5 time of interval
|
|
time.sleep( (count+0.5) * interval)
|
|
self.assertEqual(self.read_count_signaled(fd), count)
|
|
|
|
def test_timerfd_TFD_TIMER_ABSTIME(self):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
now = time.clock_gettime(time.CLOCK_REALTIME)
|
|
|
|
# 1 second later from now.
|
|
offset = 1
|
|
initial_expiration = now + offset
|
|
# not interval timer
|
|
interval = 0
|
|
|
|
os.timerfd_settime(fd, flags=os.TFD_TIMER_ABSTIME, initial=initial_expiration, interval=interval)
|
|
|
|
# timerfd_gettime
|
|
# Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified.
|
|
next_expiration, interval2 = os.timerfd_gettime(fd)
|
|
self.assertAlmostEqual(interval2, interval, places=3)
|
|
self.assertAlmostEqual(next_expiration, offset, places=3)
|
|
|
|
t = time.perf_counter()
|
|
count_signaled = self.read_count_signaled(fd)
|
|
t = time.perf_counter() - t
|
|
self.assertEqual(count_signaled, 1)
|
|
|
|
self.assertGreater(t, offset - self.CLOCK_RES)
|
|
|
|
def test_timerfd_select(self):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
|
|
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
|
|
self.assertEqual((rfd, wfd, xfd), ([], [], []))
|
|
|
|
# 0.25 second
|
|
initial_expiration = 0.25
|
|
# every 0.125 second
|
|
interval = 0.125
|
|
|
|
os.timerfd_settime(fd, initial=initial_expiration, interval=interval)
|
|
|
|
count = 3
|
|
t = time.perf_counter()
|
|
for _ in range(count):
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], initial_expiration + interval)
|
|
self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
t = time.perf_counter() - t
|
|
|
|
total_time = initial_expiration + interval * (count - 1)
|
|
self.assertGreater(t, total_time - self.CLOCK_RES)
|
|
|
|
def check_timerfd_poll(self, nanoseconds):
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
|
|
|
|
selector = selectors.DefaultSelector()
|
|
selector.register(fd, selectors.EVENT_READ)
|
|
self.addCleanup(selector.close)
|
|
|
|
sec_to_nsec = 10 ** 9
|
|
# 0.25 second
|
|
initial_expiration_ns = sec_to_nsec // 4
|
|
# every 0.125 second
|
|
interval_ns = sec_to_nsec // 8
|
|
|
|
if nanoseconds:
|
|
os.timerfd_settime_ns(fd,
|
|
initial=initial_expiration_ns,
|
|
interval=interval_ns)
|
|
else:
|
|
os.timerfd_settime(fd,
|
|
initial=initial_expiration_ns / sec_to_nsec,
|
|
interval=interval_ns / sec_to_nsec)
|
|
|
|
count = 3
|
|
if nanoseconds:
|
|
t = time.perf_counter_ns()
|
|
else:
|
|
t = time.perf_counter()
|
|
for i in range(count):
|
|
timeout_margin_ns = interval_ns
|
|
if i == 0:
|
|
timeout_ns = initial_expiration_ns + interval_ns + timeout_margin_ns
|
|
else:
|
|
timeout_ns = interval_ns + timeout_margin_ns
|
|
|
|
ready = selector.select(timeout_ns / sec_to_nsec)
|
|
self.assertEqual(len(ready), 1, ready)
|
|
event = ready[0][1]
|
|
self.assertEqual(event, selectors.EVENT_READ)
|
|
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
|
|
total_time = initial_expiration_ns + interval_ns * (count - 1)
|
|
if nanoseconds:
|
|
dt = time.perf_counter_ns() - t
|
|
self.assertGreater(dt, total_time - self.CLOCK_RES_NS)
|
|
else:
|
|
dt = time.perf_counter() - t
|
|
self.assertGreater(dt, total_time / sec_to_nsec - self.CLOCK_RES)
|
|
selector.unregister(fd)
|
|
|
|
def test_timerfd_poll(self):
|
|
self.check_timerfd_poll(False)
|
|
|
|
def test_timerfd_ns_poll(self):
|
|
self.check_timerfd_poll(True)
|
|
|
|
def test_timerfd_ns_initval(self):
|
|
one_sec_in_nsec = 10**9
|
|
limit_error = one_sec_in_nsec // 10**3
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
# 1st call
|
|
initial_expiration_ns = 0
|
|
interval_ns = one_sec_in_nsec // 1000
|
|
next_expiration_ns, interval_ns2 = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
|
|
self.assertEqual(interval_ns2, 0)
|
|
self.assertEqual(next_expiration_ns, 0)
|
|
|
|
# 2nd call
|
|
next_expiration_ns, interval_ns2 = os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
|
|
self.assertEqual(interval_ns2, interval_ns)
|
|
self.assertEqual(next_expiration_ns, initial_expiration_ns)
|
|
|
|
# timerfd_gettime
|
|
next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
|
|
self.assertEqual(interval_ns2, interval_ns)
|
|
self.assertLessEqual(next_expiration_ns, initial_expiration_ns)
|
|
|
|
self.assertAlmostEqual(next_expiration_ns, initial_expiration_ns, delta=limit_error)
|
|
|
|
def test_timerfd_ns_interval(self):
|
|
one_sec_in_nsec = 10**9
|
|
limit_error = one_sec_in_nsec // 10**3
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
# 1 second
|
|
initial_expiration_ns = one_sec_in_nsec
|
|
# every 0.5 second
|
|
interval_ns = one_sec_in_nsec // 2
|
|
|
|
os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
|
|
|
|
# timerfd_gettime
|
|
next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
|
|
self.assertEqual(interval_ns2, interval_ns)
|
|
self.assertLessEqual(next_expiration_ns, initial_expiration_ns)
|
|
|
|
count = 3
|
|
t = time.perf_counter_ns()
|
|
for _ in range(count):
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
t = time.perf_counter_ns() - t
|
|
|
|
total_time_ns = initial_expiration_ns + interval_ns * (count - 1)
|
|
self.assertGreater(t, total_time_ns - self.CLOCK_RES_NS)
|
|
|
|
# wait 3.5 time of interval
|
|
time.sleep( (count+0.5) * interval_ns / one_sec_in_nsec)
|
|
self.assertEqual(self.read_count_signaled(fd), count)
|
|
|
|
|
|
def test_timerfd_ns_TFD_TIMER_ABSTIME(self):
|
|
one_sec_in_nsec = 10**9
|
|
limit_error = one_sec_in_nsec // 10**3
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME)
|
|
|
|
now_ns = time.clock_gettime_ns(time.CLOCK_REALTIME)
|
|
|
|
# 1 second later from now.
|
|
offset_ns = one_sec_in_nsec
|
|
initial_expiration_ns = now_ns + offset_ns
|
|
# not interval timer
|
|
interval_ns = 0
|
|
|
|
os.timerfd_settime_ns(fd, flags=os.TFD_TIMER_ABSTIME, initial=initial_expiration_ns, interval=interval_ns)
|
|
|
|
# timerfd_gettime
|
|
# Note: timerfd_gettime returns relative values even if TFD_TIMER_ABSTIME is specified.
|
|
next_expiration_ns, interval_ns2 = os.timerfd_gettime_ns(fd)
|
|
self.assertLess(abs(interval_ns2 - interval_ns), limit_error)
|
|
self.assertLess(abs(next_expiration_ns - offset_ns), limit_error)
|
|
|
|
t = time.perf_counter_ns()
|
|
count_signaled = self.read_count_signaled(fd)
|
|
t = time.perf_counter_ns() - t
|
|
self.assertEqual(count_signaled, 1)
|
|
|
|
self.assertGreater(t, offset_ns - self.CLOCK_RES_NS)
|
|
|
|
def test_timerfd_ns_select(self):
|
|
one_sec_in_nsec = 10**9
|
|
|
|
fd = self.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
|
|
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], 0)
|
|
self.assertEqual((rfd, wfd, xfd), ([], [], []))
|
|
|
|
# 0.25 second
|
|
initial_expiration_ns = one_sec_in_nsec // 4
|
|
# every 0.125 second
|
|
interval_ns = one_sec_in_nsec // 8
|
|
|
|
os.timerfd_settime_ns(fd, initial=initial_expiration_ns, interval=interval_ns)
|
|
|
|
count = 3
|
|
t = time.perf_counter_ns()
|
|
for _ in range(count):
|
|
rfd, wfd, xfd = select.select([fd], [fd], [fd], (initial_expiration_ns + interval_ns) / 1e9 )
|
|
self.assertEqual((rfd, wfd, xfd), ([fd], [], []))
|
|
self.assertEqual(self.read_count_signaled(fd), 1)
|
|
t = time.perf_counter_ns() - t
|
|
|
|
total_time_ns = initial_expiration_ns + interval_ns * (count - 1)
|
|
self.assertGreater(t, total_time_ns - self.CLOCK_RES_NS)
|
|
|
|
class OSErrorTests(unittest.TestCase):
|
|
def setUp(self):
|
|
class Str(str):
|
|
pass
|
|
|
|
self.bytes_filenames = []
|
|
self.unicode_filenames = []
|
|
if os_helper.TESTFN_UNENCODABLE is not None:
|
|
decoded = os_helper.TESTFN_UNENCODABLE
|
|
else:
|
|
decoded = os_helper.TESTFN
|
|
self.unicode_filenames.append(decoded)
|
|
self.unicode_filenames.append(Str(decoded))
|
|
if os_helper.TESTFN_UNDECODABLE is not None:
|
|
encoded = os_helper.TESTFN_UNDECODABLE
|
|
else:
|
|
encoded = os.fsencode(os_helper.TESTFN)
|
|
self.bytes_filenames.append(encoded)
|
|
|
|
self.filenames = self.bytes_filenames + self.unicode_filenames
|
|
|
|
def test_oserror_filename(self):
|
|
funcs = [
|
|
(self.filenames, os.chdir,),
|
|
(self.filenames, os.lstat,),
|
|
(self.filenames, os.open, os.O_RDONLY),
|
|
(self.filenames, os.rmdir,),
|
|
(self.filenames, os.stat,),
|
|
(self.filenames, os.unlink,),
|
|
(self.filenames, os.listdir,),
|
|
(self.filenames, os.rename, "dst"),
|
|
(self.filenames, os.replace, "dst"),
|
|
]
|
|
if os_helper.can_chmod():
|
|
funcs.append((self.filenames, os.chmod, 0o777))
|
|
if hasattr(os, "chown"):
|
|
funcs.append((self.filenames, os.chown, 0, 0))
|
|
if hasattr(os, "lchown"):
|
|
funcs.append((self.filenames, os.lchown, 0, 0))
|
|
if hasattr(os, "truncate"):
|
|
funcs.append((self.filenames, os.truncate, 0))
|
|
if hasattr(os, "chflags"):
|
|
funcs.append((self.filenames, os.chflags, 0))
|
|
if hasattr(os, "lchflags"):
|
|
funcs.append((self.filenames, os.lchflags, 0))
|
|
if hasattr(os, "chroot"):
|
|
funcs.append((self.filenames, os.chroot,))
|
|
if hasattr(os, "link"):
|
|
funcs.append((self.filenames, os.link, "dst"))
|
|
if hasattr(os, "listxattr"):
|
|
funcs.extend((
|
|
(self.filenames, os.listxattr,),
|
|
(self.filenames, os.getxattr, "user.test"),
|
|
(self.filenames, os.setxattr, "user.test", b'user'),
|
|
(self.filenames, os.removexattr, "user.test"),
|
|
))
|
|
if hasattr(os, "lchmod"):
|
|
funcs.append((self.filenames, os.lchmod, 0o777))
|
|
if hasattr(os, "readlink"):
|
|
funcs.append((self.filenames, os.readlink,))
|
|
|
|
for filenames, func, *func_args in funcs:
|
|
for name in filenames:
|
|
try:
|
|
func(name, *func_args)
|
|
except OSError as err:
|
|
self.assertIs(err.filename, name, str(func))
|
|
except UnicodeDecodeError:
|
|
pass
|
|
else:
|
|
self.fail(f"No exception thrown by {func}")
|
|
|
|
class CPUCountTests(unittest.TestCase):
|
|
def check_cpu_count(self, cpus):
|
|
if cpus is None:
|
|
self.skipTest("Could not determine the number of CPUs")
|
|
|
|
self.assertIsInstance(cpus, int)
|
|
self.assertGreater(cpus, 0)
|
|
|
|
def test_cpu_count(self):
|
|
cpus = os.cpu_count()
|
|
self.check_cpu_count(cpus)
|
|
|
|
def test_process_cpu_count(self):
|
|
cpus = os.process_cpu_count()
|
|
self.assertLessEqual(cpus, os.cpu_count())
|
|
self.check_cpu_count(cpus)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'sched_setaffinity'),
|
|
"don't have sched affinity support")
|
|
def test_process_cpu_count_affinity(self):
|
|
affinity1 = os.process_cpu_count()
|
|
if affinity1 is None:
|
|
self.skipTest("Could not determine the number of CPUs")
|
|
|
|
# Disable one CPU
|
|
mask = os.sched_getaffinity(0)
|
|
if len(mask) <= 1:
|
|
self.skipTest(f"sched_getaffinity() returns less than "
|
|
f"2 CPUs: {sorted(mask)}")
|
|
self.addCleanup(os.sched_setaffinity, 0, list(mask))
|
|
mask.pop()
|
|
os.sched_setaffinity(0, mask)
|
|
|
|
# test process_cpu_count()
|
|
affinity2 = os.process_cpu_count()
|
|
self.assertEqual(affinity2, affinity1 - 1)
|
|
|
|
|
|
# FD inheritance check is only useful for systems with process support.
|
|
@support.requires_subprocess()
|
|
class FDInheritanceTests(unittest.TestCase):
|
|
def test_get_set_inheritable(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(os.get_inheritable(fd), False)
|
|
|
|
os.set_inheritable(fd, True)
|
|
self.assertEqual(os.get_inheritable(fd), True)
|
|
|
|
@unittest.skipIf(fcntl is None, "need fcntl")
|
|
def test_get_inheritable_cloexec(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(os.get_inheritable(fd), False)
|
|
|
|
# clear FD_CLOEXEC flag
|
|
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
|
flags &= ~fcntl.FD_CLOEXEC
|
|
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
|
|
|
|
self.assertEqual(os.get_inheritable(fd), True)
|
|
|
|
@unittest.skipIf(fcntl is None, "need fcntl")
|
|
def test_set_inheritable_cloexec(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
|
|
fcntl.FD_CLOEXEC)
|
|
|
|
os.set_inheritable(fd, True)
|
|
self.assertEqual(fcntl.fcntl(fd, fcntl.F_GETFD) & fcntl.FD_CLOEXEC,
|
|
0)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'O_PATH'), "need os.O_PATH")
|
|
def test_get_set_inheritable_o_path(self):
|
|
fd = os.open(__file__, os.O_PATH)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(os.get_inheritable(fd), False)
|
|
|
|
os.set_inheritable(fd, True)
|
|
self.assertEqual(os.get_inheritable(fd), True)
|
|
|
|
os.set_inheritable(fd, False)
|
|
self.assertEqual(os.get_inheritable(fd), False)
|
|
|
|
def test_get_set_inheritable_badf(self):
|
|
fd = os_helper.make_bad_fd()
|
|
|
|
with self.assertRaises(OSError) as ctx:
|
|
os.get_inheritable(fd)
|
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
|
|
|
with self.assertRaises(OSError) as ctx:
|
|
os.set_inheritable(fd, True)
|
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
|
|
|
with self.assertRaises(OSError) as ctx:
|
|
os.set_inheritable(fd, False)
|
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
|
|
|
def test_open(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(os.get_inheritable(fd), False)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'pipe'), "need os.pipe()")
|
|
def test_pipe(self):
|
|
rfd, wfd = os.pipe()
|
|
self.addCleanup(os.close, rfd)
|
|
self.addCleanup(os.close, wfd)
|
|
self.assertEqual(os.get_inheritable(rfd), False)
|
|
self.assertEqual(os.get_inheritable(wfd), False)
|
|
|
|
def test_dup(self):
|
|
fd1 = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd1)
|
|
|
|
fd2 = os.dup(fd1)
|
|
self.addCleanup(os.close, fd2)
|
|
self.assertEqual(os.get_inheritable(fd2), False)
|
|
|
|
def test_dup_standard_stream(self):
|
|
fd = os.dup(1)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertGreater(fd, 0)
|
|
|
|
@unittest.skipUnless(sys.platform == 'win32', 'win32-specific test')
|
|
def test_dup_nul(self):
|
|
# os.dup() was creating inheritable fds for character files.
|
|
fd1 = os.open('NUL', os.O_RDONLY)
|
|
self.addCleanup(os.close, fd1)
|
|
fd2 = os.dup(fd1)
|
|
self.addCleanup(os.close, fd2)
|
|
self.assertFalse(os.get_inheritable(fd2))
|
|
|
|
@unittest.skipUnless(hasattr(os, 'dup2'), "need os.dup2()")
|
|
def test_dup2(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
|
|
# inheritable by default
|
|
fd2 = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd2)
|
|
self.assertEqual(os.dup2(fd, fd2), fd2)
|
|
self.assertTrue(os.get_inheritable(fd2))
|
|
|
|
# force non-inheritable
|
|
fd3 = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd3)
|
|
self.assertEqual(os.dup2(fd, fd3, inheritable=False), fd3)
|
|
self.assertFalse(os.get_inheritable(fd3))
|
|
|
|
@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()")
|
|
class PseudoterminalTests(unittest.TestCase):
|
|
def open_pty(self):
|
|
"""Open a pty fd-pair, and schedule cleanup for it"""
|
|
main_fd, second_fd = os.openpty()
|
|
self.addCleanup(os.close, main_fd)
|
|
self.addCleanup(os.close, second_fd)
|
|
return main_fd, second_fd
|
|
|
|
def test_openpty(self):
|
|
main_fd, second_fd = self.open_pty()
|
|
self.assertEqual(os.get_inheritable(main_fd), False)
|
|
self.assertEqual(os.get_inheritable(second_fd), False)
|
|
|
|
@unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()")
|
|
@unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR")
|
|
@unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY")
|
|
def test_open_via_ptsname(self):
|
|
main_fd, second_fd = self.open_pty()
|
|
second_path = os.ptsname(main_fd)
|
|
reopened_second_fd = os.open(second_path, os.O_RDWR|os.O_NOCTTY)
|
|
self.addCleanup(os.close, reopened_second_fd)
|
|
os.write(reopened_second_fd, b'foo')
|
|
self.assertEqual(os.read(main_fd, 3), b'foo')
|
|
|
|
@unittest.skipUnless(hasattr(os, 'posix_openpt'), "need os.posix_openpt()")
|
|
@unittest.skipUnless(hasattr(os, 'grantpt'), "need os.grantpt()")
|
|
@unittest.skipUnless(hasattr(os, 'unlockpt'), "need os.unlockpt()")
|
|
@unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()")
|
|
@unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR")
|
|
@unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY")
|
|
def test_posix_pty_functions(self):
|
|
mother_fd = os.posix_openpt(os.O_RDWR|os.O_NOCTTY)
|
|
self.addCleanup(os.close, mother_fd)
|
|
os.grantpt(mother_fd)
|
|
os.unlockpt(mother_fd)
|
|
son_path = os.ptsname(mother_fd)
|
|
son_fd = os.open(son_path, os.O_RDWR|os.O_NOCTTY)
|
|
self.addCleanup(os.close, son_fd)
|
|
self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd))
|
|
|
|
@unittest.skipUnless(hasattr(os, 'spawnl'), "need os.spawnl()")
|
|
@support.requires_subprocess()
|
|
def test_pipe_spawnl(self):
|
|
# gh-77046: On Windows, os.pipe() file descriptors must be created with
|
|
# _O_NOINHERIT to make them non-inheritable. UCRT has no public API to
|
|
# get (_osfile(fd) & _O_NOINHERIT), so use a functional test.
|
|
#
|
|
# Make sure that fd is not inherited by a child process created by
|
|
# os.spawnl(): get_osfhandle() and dup() must fail with EBADF.
|
|
|
|
fd, fd2 = os.pipe()
|
|
self.addCleanup(os.close, fd)
|
|
self.addCleanup(os.close, fd2)
|
|
|
|
code = textwrap.dedent(f"""
|
|
import errno
|
|
import os
|
|
import test.support
|
|
try:
|
|
import msvcrt
|
|
except ImportError:
|
|
msvcrt = None
|
|
|
|
fd = {fd}
|
|
|
|
with test.support.SuppressCrashReport():
|
|
if msvcrt is not None:
|
|
try:
|
|
handle = msvcrt.get_osfhandle(fd)
|
|
except OSError as exc:
|
|
if exc.errno != errno.EBADF:
|
|
raise
|
|
# get_osfhandle(fd) failed with EBADF as expected
|
|
else:
|
|
raise Exception("get_osfhandle() must fail")
|
|
|
|
try:
|
|
fd3 = os.dup(fd)
|
|
except OSError as exc:
|
|
if exc.errno != errno.EBADF:
|
|
raise
|
|
# os.dup(fd) failed with EBADF as expected
|
|
else:
|
|
os.close(fd3)
|
|
raise Exception("dup must fail")
|
|
""")
|
|
|
|
filename = os_helper.TESTFN
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
with open(filename, "w") as fp:
|
|
print(code, file=fp, end="")
|
|
|
|
executable = sys.executable
|
|
cmd = [executable, filename]
|
|
if os.name == "nt" and " " in cmd[0]:
|
|
cmd[0] = f'"{cmd[0]}"'
|
|
exitcode = os.spawnl(os.P_WAIT, executable, *cmd)
|
|
self.assertEqual(exitcode, 0)
|
|
|
|
|
|
class PathTConverterTests(unittest.TestCase):
|
|
# tuples of (function name, allows fd arguments, additional arguments to
|
|
# function, cleanup function)
|
|
functions = [
|
|
('stat', True, (), None),
|
|
('lstat', False, (), None),
|
|
('access', False, (os.F_OK,), None),
|
|
('chflags', False, (0,), None),
|
|
('lchflags', False, (0,), None),
|
|
('open', False, (os.O_RDONLY,), getattr(os, 'close', None)),
|
|
]
|
|
|
|
def test_path_t_converter(self):
|
|
str_filename = os_helper.TESTFN
|
|
if os.name == 'nt':
|
|
bytes_fspath = bytes_filename = None
|
|
else:
|
|
bytes_filename = os.fsencode(os_helper.TESTFN)
|
|
bytes_fspath = FakePath(bytes_filename)
|
|
fd = os.open(FakePath(str_filename), os.O_WRONLY|os.O_CREAT)
|
|
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
|
self.addCleanup(os.close, fd)
|
|
|
|
int_fspath = FakePath(fd)
|
|
str_fspath = FakePath(str_filename)
|
|
|
|
for name, allow_fd, extra_args, cleanup_fn in self.functions:
|
|
with self.subTest(name=name):
|
|
try:
|
|
fn = getattr(os, name)
|
|
except AttributeError:
|
|
continue
|
|
|
|
for path in (str_filename, bytes_filename, str_fspath,
|
|
bytes_fspath):
|
|
if path is None:
|
|
continue
|
|
with self.subTest(name=name, path=path):
|
|
result = fn(path, *extra_args)
|
|
if cleanup_fn is not None:
|
|
cleanup_fn(result)
|
|
|
|
with self.assertRaisesRegex(
|
|
TypeError, 'to return str or bytes'):
|
|
fn(int_fspath, *extra_args)
|
|
|
|
if allow_fd:
|
|
result = fn(fd, *extra_args) # should not fail
|
|
if cleanup_fn is not None:
|
|
cleanup_fn(result)
|
|
else:
|
|
with self.assertRaisesRegex(
|
|
TypeError,
|
|
'os.PathLike'):
|
|
fn(fd, *extra_args)
|
|
|
|
def test_path_t_converter_and_custom_class(self):
|
|
msg = r'__fspath__\(\) to return str or bytes, not %s'
|
|
with self.assertRaisesRegex(TypeError, msg % r'int'):
|
|
os.stat(FakePath(2))
|
|
with self.assertRaisesRegex(TypeError, msg % r'float'):
|
|
os.stat(FakePath(2.34))
|
|
with self.assertRaisesRegex(TypeError, msg % r'object'):
|
|
os.stat(FakePath(object()))
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
|
'needs os.get_blocking() and os.set_blocking()')
|
|
@unittest.skipIf(support.is_emscripten, "Cannot unset blocking flag")
|
|
@unittest.skipIf(sys.platform == 'win32', 'Windows only supports blocking on pipes')
|
|
class BlockingTests(unittest.TestCase):
|
|
def test_blocking(self):
|
|
fd = os.open(__file__, os.O_RDONLY)
|
|
self.addCleanup(os.close, fd)
|
|
self.assertEqual(os.get_blocking(fd), True)
|
|
|
|
os.set_blocking(fd, False)
|
|
self.assertEqual(os.get_blocking(fd), False)
|
|
|
|
os.set_blocking(fd, True)
|
|
self.assertEqual(os.get_blocking(fd), True)
|
|
|
|
|
|
|
|
class ExportsTests(unittest.TestCase):
|
|
def test_os_all(self):
|
|
self.assertIn('open', os.__all__)
|
|
self.assertIn('walk', os.__all__)
|
|
|
|
|
|
class TestDirEntry(unittest.TestCase):
|
|
def setUp(self):
|
|
self.path = os.path.realpath(os_helper.TESTFN)
|
|
self.addCleanup(os_helper.rmtree, self.path)
|
|
os.mkdir(self.path)
|
|
|
|
def test_uninstantiable(self):
|
|
self.assertRaises(TypeError, os.DirEntry)
|
|
|
|
def test_unpickable(self):
|
|
filename = create_file(os.path.join(self.path, "file.txt"), b'python')
|
|
entry = [entry for entry in os.scandir(self.path)].pop()
|
|
self.assertIsInstance(entry, os.DirEntry)
|
|
self.assertEqual(entry.name, "file.txt")
|
|
import pickle
|
|
self.assertRaises(TypeError, pickle.dumps, entry, filename)
|
|
|
|
|
|
class TestScandir(unittest.TestCase):
|
|
check_no_resource_warning = warnings_helper.check_no_resource_warning
|
|
|
|
def setUp(self):
|
|
self.path = os.path.realpath(os_helper.TESTFN)
|
|
self.bytes_path = os.fsencode(self.path)
|
|
self.addCleanup(os_helper.rmtree, self.path)
|
|
os.mkdir(self.path)
|
|
|
|
def create_file(self, name="file.txt"):
|
|
path = self.bytes_path if isinstance(name, bytes) else self.path
|
|
filename = os.path.join(path, name)
|
|
create_file(filename, b'python')
|
|
return filename
|
|
|
|
def get_entries(self, names):
|
|
entries = dict((entry.name, entry)
|
|
for entry in os.scandir(self.path))
|
|
self.assertEqual(sorted(entries.keys()), names)
|
|
return entries
|
|
|
|
def assert_stat_equal(self, stat1, stat2, skip_fields):
|
|
if skip_fields:
|
|
for attr in dir(stat1):
|
|
if not attr.startswith("st_"):
|
|
continue
|
|
if attr in ("st_dev", "st_ino", "st_nlink", "st_ctime",
|
|
"st_ctime_ns"):
|
|
continue
|
|
self.assertEqual(getattr(stat1, attr),
|
|
getattr(stat2, attr),
|
|
(stat1, stat2, attr))
|
|
else:
|
|
self.assertEqual(stat1, stat2)
|
|
|
|
def test_uninstantiable(self):
|
|
scandir_iter = os.scandir(self.path)
|
|
self.assertRaises(TypeError, type(scandir_iter))
|
|
scandir_iter.close()
|
|
|
|
def test_unpickable(self):
|
|
filename = self.create_file("file.txt")
|
|
scandir_iter = os.scandir(self.path)
|
|
import pickle
|
|
self.assertRaises(TypeError, pickle.dumps, scandir_iter, filename)
|
|
scandir_iter.close()
|
|
|
|
def check_entry(self, entry, name, is_dir, is_file, is_symlink):
|
|
self.assertIsInstance(entry, os.DirEntry)
|
|
self.assertEqual(entry.name, name)
|
|
self.assertEqual(entry.path, os.path.join(self.path, name))
|
|
self.assertEqual(entry.inode(),
|
|
os.stat(entry.path, follow_symlinks=False).st_ino)
|
|
|
|
entry_stat = os.stat(entry.path)
|
|
self.assertEqual(entry.is_dir(),
|
|
stat.S_ISDIR(entry_stat.st_mode))
|
|
self.assertEqual(entry.is_file(),
|
|
stat.S_ISREG(entry_stat.st_mode))
|
|
self.assertEqual(entry.is_symlink(),
|
|
os.path.islink(entry.path))
|
|
|
|
entry_lstat = os.stat(entry.path, follow_symlinks=False)
|
|
self.assertEqual(entry.is_dir(follow_symlinks=False),
|
|
stat.S_ISDIR(entry_lstat.st_mode))
|
|
self.assertEqual(entry.is_file(follow_symlinks=False),
|
|
stat.S_ISREG(entry_lstat.st_mode))
|
|
|
|
self.assertEqual(entry.is_junction(), os.path.isjunction(entry.path))
|
|
|
|
self.assert_stat_equal(entry.stat(),
|
|
entry_stat,
|
|
os.name == 'nt' and not is_symlink)
|
|
self.assert_stat_equal(entry.stat(follow_symlinks=False),
|
|
entry_lstat,
|
|
os.name == 'nt')
|
|
|
|
def test_attributes(self):
|
|
link = os_helper.can_hardlink()
|
|
symlink = os_helper.can_symlink()
|
|
|
|
dirname = os.path.join(self.path, "dir")
|
|
os.mkdir(dirname)
|
|
filename = self.create_file("file.txt")
|
|
if link:
|
|
try:
|
|
os.link(filename, os.path.join(self.path, "link_file.txt"))
|
|
except PermissionError as e:
|
|
self.skipTest('os.link(): %s' % e)
|
|
if symlink:
|
|
os.symlink(dirname, os.path.join(self.path, "symlink_dir"),
|
|
target_is_directory=True)
|
|
os.symlink(filename, os.path.join(self.path, "symlink_file.txt"))
|
|
|
|
names = ['dir', 'file.txt']
|
|
if link:
|
|
names.append('link_file.txt')
|
|
if symlink:
|
|
names.extend(('symlink_dir', 'symlink_file.txt'))
|
|
entries = self.get_entries(names)
|
|
|
|
entry = entries['dir']
|
|
self.check_entry(entry, 'dir', True, False, False)
|
|
|
|
entry = entries['file.txt']
|
|
self.check_entry(entry, 'file.txt', False, True, False)
|
|
|
|
if link:
|
|
entry = entries['link_file.txt']
|
|
self.check_entry(entry, 'link_file.txt', False, True, False)
|
|
|
|
if symlink:
|
|
entry = entries['symlink_dir']
|
|
self.check_entry(entry, 'symlink_dir', True, False, True)
|
|
|
|
entry = entries['symlink_file.txt']
|
|
self.check_entry(entry, 'symlink_file.txt', False, True, True)
|
|
|
|
@unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.")
|
|
def test_attributes_junctions(self):
|
|
dirname = os.path.join(self.path, "tgtdir")
|
|
os.mkdir(dirname)
|
|
|
|
import _winapi
|
|
try:
|
|
_winapi.CreateJunction(dirname, os.path.join(self.path, "srcjunc"))
|
|
except OSError:
|
|
raise unittest.SkipTest('creating the test junction failed')
|
|
|
|
entries = self.get_entries(['srcjunc', 'tgtdir'])
|
|
self.assertEqual(entries['srcjunc'].is_junction(), True)
|
|
self.assertEqual(entries['tgtdir'].is_junction(), False)
|
|
|
|
def get_entry(self, name):
|
|
path = self.bytes_path if isinstance(name, bytes) else self.path
|
|
entries = list(os.scandir(path))
|
|
self.assertEqual(len(entries), 1)
|
|
|
|
entry = entries[0]
|
|
self.assertEqual(entry.name, name)
|
|
return entry
|
|
|
|
def create_file_entry(self, name='file.txt'):
|
|
filename = self.create_file(name=name)
|
|
return self.get_entry(os.path.basename(filename))
|
|
|
|
def test_current_directory(self):
|
|
filename = self.create_file()
|
|
old_dir = os.getcwd()
|
|
try:
|
|
os.chdir(self.path)
|
|
|
|
# call scandir() without parameter: it must list the content
|
|
# of the current directory
|
|
entries = dict((entry.name, entry) for entry in os.scandir())
|
|
self.assertEqual(sorted(entries.keys()),
|
|
[os.path.basename(filename)])
|
|
finally:
|
|
os.chdir(old_dir)
|
|
|
|
def test_repr(self):
|
|
entry = self.create_file_entry()
|
|
self.assertEqual(repr(entry), "<DirEntry 'file.txt'>")
|
|
|
|
def test_fspath_protocol(self):
|
|
entry = self.create_file_entry()
|
|
self.assertEqual(os.fspath(entry), os.path.join(self.path, 'file.txt'))
|
|
|
|
def test_fspath_protocol_bytes(self):
|
|
bytes_filename = os.fsencode('bytesfile.txt')
|
|
bytes_entry = self.create_file_entry(name=bytes_filename)
|
|
fspath = os.fspath(bytes_entry)
|
|
self.assertIsInstance(fspath, bytes)
|
|
self.assertEqual(fspath,
|
|
os.path.join(os.fsencode(self.path),bytes_filename))
|
|
|
|
def test_removed_dir(self):
|
|
path = os.path.join(self.path, 'dir')
|
|
|
|
os.mkdir(path)
|
|
entry = self.get_entry('dir')
|
|
os.rmdir(path)
|
|
|
|
# On POSIX, is_dir() result depends if scandir() filled d_type or not
|
|
if os.name == 'nt':
|
|
self.assertTrue(entry.is_dir())
|
|
self.assertFalse(entry.is_file())
|
|
self.assertFalse(entry.is_symlink())
|
|
if os.name == 'nt':
|
|
self.assertRaises(FileNotFoundError, entry.inode)
|
|
# don't fail
|
|
entry.stat()
|
|
entry.stat(follow_symlinks=False)
|
|
else:
|
|
self.assertGreater(entry.inode(), 0)
|
|
self.assertRaises(FileNotFoundError, entry.stat)
|
|
self.assertRaises(FileNotFoundError, entry.stat, follow_symlinks=False)
|
|
|
|
def test_removed_file(self):
|
|
entry = self.create_file_entry()
|
|
os.unlink(entry.path)
|
|
|
|
self.assertFalse(entry.is_dir())
|
|
# On POSIX, is_dir() result depends if scandir() filled d_type or not
|
|
if os.name == 'nt':
|
|
self.assertTrue(entry.is_file())
|
|
self.assertFalse(entry.is_symlink())
|
|
if os.name == 'nt':
|
|
self.assertRaises(FileNotFoundError, entry.inode)
|
|
# don't fail
|
|
entry.stat()
|
|
entry.stat(follow_symlinks=False)
|
|
else:
|
|
self.assertGreater(entry.inode(), 0)
|
|
self.assertRaises(FileNotFoundError, entry.stat)
|
|
self.assertRaises(FileNotFoundError, entry.stat, follow_symlinks=False)
|
|
|
|
def test_broken_symlink(self):
|
|
if not os_helper.can_symlink():
|
|
return self.skipTest('cannot create symbolic link')
|
|
|
|
filename = self.create_file("file.txt")
|
|
os.symlink(filename,
|
|
os.path.join(self.path, "symlink.txt"))
|
|
entries = self.get_entries(['file.txt', 'symlink.txt'])
|
|
entry = entries['symlink.txt']
|
|
os.unlink(filename)
|
|
|
|
self.assertGreater(entry.inode(), 0)
|
|
self.assertFalse(entry.is_dir())
|
|
self.assertFalse(entry.is_file()) # broken symlink returns False
|
|
self.assertFalse(entry.is_dir(follow_symlinks=False))
|
|
self.assertFalse(entry.is_file(follow_symlinks=False))
|
|
self.assertTrue(entry.is_symlink())
|
|
self.assertRaises(FileNotFoundError, entry.stat)
|
|
# don't fail
|
|
entry.stat(follow_symlinks=False)
|
|
|
|
def test_bytes(self):
|
|
self.create_file("file.txt")
|
|
|
|
path_bytes = os.fsencode(self.path)
|
|
entries = list(os.scandir(path_bytes))
|
|
self.assertEqual(len(entries), 1, entries)
|
|
entry = entries[0]
|
|
|
|
self.assertEqual(entry.name, b'file.txt')
|
|
self.assertEqual(entry.path,
|
|
os.fsencode(os.path.join(self.path, 'file.txt')))
|
|
|
|
def test_bytes_like(self):
|
|
self.create_file("file.txt")
|
|
|
|
for cls in bytearray, memoryview:
|
|
path_bytes = cls(os.fsencode(self.path))
|
|
with self.assertRaises(TypeError):
|
|
os.scandir(path_bytes)
|
|
|
|
@unittest.skipUnless(os.listdir in os.supports_fd,
|
|
'fd support for listdir required for this test.')
|
|
def test_fd(self):
|
|
self.assertIn(os.scandir, os.supports_fd)
|
|
self.create_file('file.txt')
|
|
expected_names = ['file.txt']
|
|
if os_helper.can_symlink():
|
|
os.symlink('file.txt', os.path.join(self.path, 'link'))
|
|
expected_names.append('link')
|
|
|
|
with os_helper.open_dir_fd(self.path) as fd:
|
|
with os.scandir(fd) as it:
|
|
entries = list(it)
|
|
names = [entry.name for entry in entries]
|
|
self.assertEqual(sorted(names), expected_names)
|
|
self.assertEqual(names, os.listdir(fd))
|
|
for entry in entries:
|
|
self.assertEqual(entry.path, entry.name)
|
|
self.assertEqual(os.fspath(entry), entry.name)
|
|
self.assertEqual(entry.is_symlink(), entry.name == 'link')
|
|
if os.stat in os.supports_dir_fd:
|
|
st = os.stat(entry.name, dir_fd=fd)
|
|
self.assertEqual(entry.stat(), st)
|
|
st = os.stat(entry.name, dir_fd=fd, follow_symlinks=False)
|
|
self.assertEqual(entry.stat(follow_symlinks=False), st)
|
|
|
|
@unittest.skipIf(support.is_wasi, "WASI maps '' to cwd")
|
|
def test_empty_path(self):
|
|
self.assertRaises(FileNotFoundError, os.scandir, '')
|
|
|
|
def test_consume_iterator_twice(self):
|
|
self.create_file("file.txt")
|
|
iterator = os.scandir(self.path)
|
|
|
|
entries = list(iterator)
|
|
self.assertEqual(len(entries), 1, entries)
|
|
|
|
# check than consuming the iterator twice doesn't raise exception
|
|
entries2 = list(iterator)
|
|
self.assertEqual(len(entries2), 0, entries2)
|
|
|
|
def test_bad_path_type(self):
|
|
for obj in [1.234, {}, []]:
|
|
self.assertRaises(TypeError, os.scandir, obj)
|
|
|
|
def test_close(self):
|
|
self.create_file("file.txt")
|
|
self.create_file("file2.txt")
|
|
iterator = os.scandir(self.path)
|
|
next(iterator)
|
|
iterator.close()
|
|
# multiple closes
|
|
iterator.close()
|
|
with self.check_no_resource_warning():
|
|
del iterator
|
|
|
|
def test_context_manager(self):
|
|
self.create_file("file.txt")
|
|
self.create_file("file2.txt")
|
|
with os.scandir(self.path) as iterator:
|
|
next(iterator)
|
|
with self.check_no_resource_warning():
|
|
del iterator
|
|
|
|
def test_context_manager_close(self):
|
|
self.create_file("file.txt")
|
|
self.create_file("file2.txt")
|
|
with os.scandir(self.path) as iterator:
|
|
next(iterator)
|
|
iterator.close()
|
|
|
|
def test_context_manager_exception(self):
|
|
self.create_file("file.txt")
|
|
self.create_file("file2.txt")
|
|
with self.assertRaises(ZeroDivisionError):
|
|
with os.scandir(self.path) as iterator:
|
|
next(iterator)
|
|
1/0
|
|
with self.check_no_resource_warning():
|
|
del iterator
|
|
|
|
def test_resource_warning(self):
|
|
self.create_file("file.txt")
|
|
self.create_file("file2.txt")
|
|
iterator = os.scandir(self.path)
|
|
next(iterator)
|
|
with self.assertWarns(ResourceWarning):
|
|
del iterator
|
|
support.gc_collect()
|
|
# exhausted iterator
|
|
iterator = os.scandir(self.path)
|
|
list(iterator)
|
|
with self.check_no_resource_warning():
|
|
del iterator
|
|
|
|
|
|
class TestPEP519(unittest.TestCase):
|
|
|
|
# Abstracted so it can be overridden to test pure Python implementation
|
|
# if a C version is provided.
|
|
fspath = staticmethod(os.fspath)
|
|
|
|
def test_return_bytes(self):
|
|
for b in b'hello', b'goodbye', b'some/path/and/file':
|
|
self.assertEqual(b, self.fspath(b))
|
|
|
|
def test_return_string(self):
|
|
for s in 'hello', 'goodbye', 'some/path/and/file':
|
|
self.assertEqual(s, self.fspath(s))
|
|
|
|
def test_fsencode_fsdecode(self):
|
|
for p in "path/like/object", b"path/like/object":
|
|
pathlike = FakePath(p)
|
|
|
|
self.assertEqual(p, self.fspath(pathlike))
|
|
self.assertEqual(b"path/like/object", os.fsencode(pathlike))
|
|
self.assertEqual("path/like/object", os.fsdecode(pathlike))
|
|
|
|
def test_pathlike(self):
|
|
self.assertEqual('#feelthegil', self.fspath(FakePath('#feelthegil')))
|
|
self.assertTrue(issubclass(FakePath, os.PathLike))
|
|
self.assertTrue(isinstance(FakePath('x'), os.PathLike))
|
|
|
|
def test_garbage_in_exception_out(self):
|
|
vapor = type('blah', (), {})
|
|
for o in int, type, os, vapor():
|
|
self.assertRaises(TypeError, self.fspath, o)
|
|
|
|
def test_argument_required(self):
|
|
self.assertRaises(TypeError, self.fspath)
|
|
|
|
def test_bad_pathlike(self):
|
|
# __fspath__ returns a value other than str or bytes.
|
|
self.assertRaises(TypeError, self.fspath, FakePath(42))
|
|
# __fspath__ attribute that is not callable.
|
|
c = type('foo', (), {})
|
|
c.__fspath__ = 1
|
|
self.assertRaises(TypeError, self.fspath, c())
|
|
# __fspath__ raises an exception.
|
|
self.assertRaises(ZeroDivisionError, self.fspath,
|
|
FakePath(ZeroDivisionError()))
|
|
|
|
def test_pathlike_subclasshook(self):
|
|
# bpo-38878: subclasshook causes subclass checks
|
|
# true on abstract implementation.
|
|
class A(os.PathLike):
|
|
pass
|
|
self.assertFalse(issubclass(FakePath, A))
|
|
self.assertTrue(issubclass(FakePath, os.PathLike))
|
|
|
|
def test_pathlike_class_getitem(self):
|
|
self.assertIsInstance(os.PathLike[bytes], types.GenericAlias)
|
|
|
|
def test_pathlike_subclass_slots(self):
|
|
class A(os.PathLike):
|
|
__slots__ = ()
|
|
def __fspath__(self):
|
|
return ''
|
|
self.assertFalse(hasattr(A(), '__dict__'))
|
|
|
|
def test_fspath_set_to_None(self):
|
|
class Foo:
|
|
__fspath__ = None
|
|
|
|
class Bar:
|
|
def __fspath__(self):
|
|
return 'bar'
|
|
|
|
class Baz(Bar):
|
|
__fspath__ = None
|
|
|
|
good_error_msg = (
|
|
r"expected str, bytes or os.PathLike object, not {}".format
|
|
)
|
|
|
|
with self.assertRaisesRegex(TypeError, good_error_msg("Foo")):
|
|
self.fspath(Foo())
|
|
|
|
self.assertEqual(self.fspath(Bar()), 'bar')
|
|
|
|
with self.assertRaisesRegex(TypeError, good_error_msg("Baz")):
|
|
self.fspath(Baz())
|
|
|
|
with self.assertRaisesRegex(TypeError, good_error_msg("Foo")):
|
|
open(Foo())
|
|
|
|
with self.assertRaisesRegex(TypeError, good_error_msg("Baz")):
|
|
open(Baz())
|
|
|
|
other_good_error_msg = (
|
|
r"should be string, bytes or os.PathLike, not {}".format
|
|
)
|
|
|
|
with self.assertRaisesRegex(TypeError, other_good_error_msg("Foo")):
|
|
os.rename(Foo(), "foooo")
|
|
|
|
with self.assertRaisesRegex(TypeError, other_good_error_msg("Baz")):
|
|
os.rename(Baz(), "bazzz")
|
|
|
|
class TimesTests(unittest.TestCase):
|
|
def test_times(self):
|
|
times = os.times()
|
|
self.assertIsInstance(times, os.times_result)
|
|
|
|
for field in ('user', 'system', 'children_user', 'children_system',
|
|
'elapsed'):
|
|
value = getattr(times, field)
|
|
self.assertIsInstance(value, float)
|
|
|
|
if os.name == 'nt':
|
|
self.assertEqual(times.children_user, 0)
|
|
self.assertEqual(times.children_system, 0)
|
|
self.assertEqual(times.elapsed, 0)
|
|
|
|
|
|
@support.requires_fork()
|
|
class ForkTests(unittest.TestCase):
|
|
def test_fork(self):
|
|
# bpo-42540: ensure os.fork() with non-default memory allocator does
|
|
# not crash on exit.
|
|
code = """if 1:
|
|
import os
|
|
from test import support
|
|
pid = os.fork()
|
|
if pid != 0:
|
|
support.wait_process(pid, exitcode=0)
|
|
"""
|
|
assert_python_ok("-c", code)
|
|
if support.Py_GIL_DISABLED:
|
|
assert_python_ok("-c", code, PYTHONMALLOC="mimalloc_debug")
|
|
else:
|
|
assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug")
|
|
|
|
@unittest.skipUnless(sys.platform in ("linux", "android", "darwin"),
|
|
"Only Linux and macOS detect this today.")
|
|
def test_fork_warns_when_non_python_thread_exists(self):
|
|
code = """if 1:
|
|
import os, threading, warnings
|
|
from _testcapi import _spawn_pthread_waiter, _end_spawned_pthread
|
|
_spawn_pthread_waiter()
|
|
try:
|
|
with warnings.catch_warnings(record=True) as ws:
|
|
warnings.filterwarnings(
|
|
"always", category=DeprecationWarning)
|
|
if os.fork() == 0:
|
|
assert not ws, f"unexpected warnings in child: {ws}"
|
|
os._exit(0) # child
|
|
else:
|
|
assert ws[0].category == DeprecationWarning, ws[0]
|
|
assert 'fork' in str(ws[0].message), ws[0]
|
|
# Waiting allows an error in the child to hit stderr.
|
|
exitcode = os.wait()[1]
|
|
assert exitcode == 0, f"child exited {exitcode}"
|
|
assert threading.active_count() == 1, threading.enumerate()
|
|
finally:
|
|
_end_spawned_pthread()
|
|
"""
|
|
_, out, err = assert_python_ok("-c", code, PYTHONOPTIMIZE='0')
|
|
self.assertEqual(err.decode("utf-8"), "")
|
|
self.assertEqual(out.decode("utf-8"), "")
|
|
|
|
def test_fork_at_exit(self):
|
|
code = """if 1:
|
|
import atexit
|
|
import os
|
|
|
|
def exit_handler():
|
|
pid = os.fork()
|
|
if pid != 0:
|
|
print("shouldn't be printed")
|
|
|
|
atexit.register(exit_handler)
|
|
"""
|
|
_, out, err = assert_python_ok("-c", code)
|
|
self.assertEqual(b"", out)
|
|
self.assertIn(b"can't fork at interpreter shutdown", err)
|
|
|
|
|
|
# Only test if the C version is provided, otherwise TestPEP519 already tested
|
|
# the pure Python implementation.
|
|
if hasattr(os, "_fspath"):
|
|
class TestPEP519PurePython(TestPEP519):
|
|
|
|
"""Explicitly test the pure Python implementation of os.fspath()."""
|
|
|
|
fspath = staticmethod(os._fspath)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|