diff --git a/Lib/test/libregrtest/findtests.py b/Lib/test/libregrtest/findtests.py index 79afaf9083ae59..6c0e50846a466b 100644 --- a/Lib/test/libregrtest/findtests.py +++ b/Lib/test/libregrtest/findtests.py @@ -25,10 +25,11 @@ "test_gdb", "test_inspect", "test_io", - "test_pydoc", "test_multiprocessing_fork", "test_multiprocessing_forkserver", "test_multiprocessing_spawn", + "test_os", + "test_pydoc", } diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py deleted file mode 100644 index 1180e27a7a5310..00000000000000 --- a/Lib/test/test_os.py +++ /dev/null @@ -1,5662 +0,0 @@ -# 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: - import _testcapi - from _testcapi import INT_MAX, PY_SSIZE_T_MAX -except ImportError: - _testcapi = None - 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.events._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_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") - - def test_readinto(self): - with open(os_helper.TESTFN, "w+b") as fobj: - fobj.write(b"spam") - fobj.flush() - fd = fobj.fileno() - os.lseek(fd, 0, 0) - # Oversized so readinto without hitting end. - buffer = bytearray(7) - s = os.readinto(fd, buffer) - self.assertEqual(type(s), int) - self.assertEqual(s, 4) - # Should overwrite the first 4 bytes of the buffer. - self.assertEqual(buffer[:4], b"spam") - - # Readinto at EOF should return 0 and not touch buffer. - buffer[:] = b"notspam" - s = os.readinto(fd, buffer) - self.assertEqual(type(s), int) - self.assertEqual(s, 0) - self.assertEqual(bytes(buffer), b"notspam") - s = os.readinto(fd, buffer) - self.assertEqual(s, 0) - self.assertEqual(bytes(buffer), b"notspam") - - # Readinto a 0 length bytearray when at EOF should return 0 - self.assertEqual(os.readinto(fd, bytearray()), 0) - - # Readinto a 0 length bytearray with data available should return 0. - os.lseek(fd, 0, 0) - self.assertEqual(os.readinto(fd, bytearray()), 0) - - @unittest.skipUnless(hasattr(os, 'get_blocking'), - 'needs os.get_blocking() and os.set_blocking()') - @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") - @unittest.skipIf(support.is_emscripten, "set_blocking does not work correctly") - def test_readinto_non_blocking(self): - # Verify behavior of a readinto which would block on a non-blocking fd. - r, w = os.pipe() - try: - os.set_blocking(r, False) - with self.assertRaises(BlockingIOError): - os.readinto(r, bytearray(5)) - - # Pass some data through - os.write(w, b"spam") - self.assertEqual(os.readinto(r, bytearray(4)), 4) - - # Still don't block or return 0. - with self.assertRaises(BlockingIOError): - os.readinto(r, bytearray(5)) - - # At EOF should return size 0 - os.close(w) - w = None - self.assertEqual(os.readinto(r, bytearray(5)), 0) - self.assertEqual(os.readinto(r, bytearray(5)), 0) # Still EOF - - finally: - os.close(r) - if w is not None: - os.close(w) - - def test_readinto_badarg(self): - with open(os_helper.TESTFN, "w+b") as fobj: - fobj.write(b"spam") - fobj.flush() - fd = fobj.fileno() - os.lseek(fd, 0, 0) - - for bad_arg in ("test", bytes(), 14): - with self.subTest(f"bad buffer {type(bad_arg)}"): - with self.assertRaises(TypeError): - os.readinto(fd, bad_arg) - - with self.subTest("doesn't work on file objects"): - with self.assertRaises(TypeError): - os.readinto(fobj, bytearray(5)) - - # takes two args - with self.assertRaises(TypeError): - os.readinto(fd) - - # No data should have been read with the bad arguments. - buffer = bytearray(4) - s = os.readinto(fd, buffer) - self.assertEqual(s, 4) - self.assertEqual(buffer, 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') - - - @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_readinto(self, size): - self.addCleanup(os_helper.unlink, os_helper.TESTFN) - create_file(os_helper.TESTFN, b'test') - - # Issue #21932: For readinto the buffer contains the length rather than - # a length being passed explicitly to read, should still get capped to a - # valid size / not raise an OverflowError for sizes larger than INT_MAX. - buffer = bytearray(INT_MAX + 10) - with open(os_helper.TESTFN, "rb") as fp: - length = os.readinto(fp.fileno(), buffer) - - # 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(length, 4) - self.assertEqual(buffer[:4], 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.assertHasAttr(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.is_emscripten: - # Emscripten timestamps are roundtripped through a 53 bit integer of - # nanoseconds. If we want to represent ~50 years which is an 11 - # digits number of seconds: - # 2*log10(60) + log10(24) + log10(365) + log10(60) + log10(50) - # is about 11. Because 53 * log10(2) is about 16, we only have 5 - # digits worth of sub-second precision. - # Some day it would be good to fix this upstream. - delta=1e-5 - self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5) - self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5) - self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5) - self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5) - else: - 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 - - @staticmethod - def ns_to_sec_decimal(ns): - # Convert a number of nanosecond (int) to a number of seconds (Decimal). - # Round towards infinity by adding 0.5 nanosecond to avoid rounding - # issue, os.utime() rounds towards minus infinity. - return decimal.Decimal('1e-9') * ns + decimal.Decimal('0.5e-9') - - @staticmethod - def ns_to_sec_fraction(ns): - # Convert a number of nanosecond (int) to a number of seconds (Fraction). - # Round towards infinity by adding 0.5 nanosecond to avoid rounding - # issue, os.utime() rounds towards minus infinity. - return fractions.Fraction(ns, 10**9) + fractions.Fraction(1, 2*10**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) - - def test_utime_decimal(self): - # pass times as Decimal seconds - def set_time(filename, ns): - atime_ns, mtime_ns = ns - atime = self.ns_to_sec_decimal(atime_ns) - mtime = self.ns_to_sec_decimal(mtime_ns) - os.utime(filename, (atime, mtime)) - self._test_utime(set_time) - - def test_utime_fraction(self): - # pass times as Fraction seconds - def set_time(filename, ns): - atime_ns, mtime_ns = ns - atime = self.ns_to_sec_fraction(atime_ns) - mtime = self.ns_to_sec_fraction(mtime_ns) - os.utime(filename, (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") - - times = ( - 5000000000, # some day in 2128 - # boundaries of the fast path cutoff in posixmodule.c:fill_time - -9223372037, -9223372036, 9223372035, 9223372036, - ) - for large in times: - with self.subTest(large=large): - 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) - - def test_reload_environ(self): - # Test os.reload_environ() - has_environb = hasattr(os, 'environb') - - # Test with putenv() which doesn't update os.environ - os.environ['test_env'] = 'python_value' - os.putenv("test_env", "new_value") - self.assertEqual(os.environ['test_env'], 'python_value') - if has_environb: - self.assertEqual(os.environb[b'test_env'], b'python_value') - - os.reload_environ() - self.assertEqual(os.environ['test_env'], 'new_value') - if has_environb: - self.assertEqual(os.environb[b'test_env'], b'new_value') - - # Test with unsetenv() which doesn't update os.environ - os.unsetenv('test_env') - self.assertEqual(os.environ['test_env'], 'new_value') - if has_environb: - self.assertEqual(os.environb[b'test_env'], b'new_value') - - os.reload_environ() - self.assertNotIn('test_env', os.environ) - if has_environb: - self.assertNotIn(b'test_env', os.environb) - - if has_environb: - # test reload_environ() on os.environb with putenv() - os.environb[b'test_env'] = b'python_value2' - os.putenv("test_env", "new_value2") - self.assertEqual(os.environb[b'test_env'], b'python_value2') - self.assertEqual(os.environ['test_env'], 'python_value2') - - os.reload_environ() - self.assertEqual(os.environb[b'test_env'], b'new_value2') - self.assertEqual(os.environ['test_env'], 'new_value2') - - # test reload_environ() on os.environb with unsetenv() - os.unsetenv('test_env') - self.assertEqual(os.environb[b'test_env'], b'new_value2') - self.assertEqual(os.environ['test_env'], 'new_value2') - - os.reload_environ() - self.assertNotIn(b'test_env', os.environb) - self.assertNotIn('test_env', os.environ) - -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"]) - - 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_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) - - @unittest.skipIf( - support.is_android, "dup return value is unpredictable on Android" - ) - def test_fd_finalization(self): - # Check that close()ing the fwalk() generator closes FDs - def getfd(): - fd = os.dup(1) - os.close(fd) - return fd - for topdown in (False, True): - old_fd = getfd() - it = self.fwalk(os_helper.TESTFN, topdown=topdown) - self.assertEqual(getfd(), old_fd) - next(it) - self.assertGreater(getfd(), old_fd) - it.close() - self.assertEqual(getfd(), old_fd) - - # fwalk() keeps file descriptors open - test_walk_many_open_files = 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_wasi, - "WASI's umask is a stub." - ) - def test_mode(self): - # Note: in some cases, the umask might already be 2 in which case this - # will pass even if os.umask is actually broken. - 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_wasi, - "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_wasi, - "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) - - @unittest.skipUnless(os.name == 'nt', "requires Windows") - def test_win32_mkdir_700(self): - base = os_helper.TESTFN - path = os.path.abspath(os.path.join(os_helper.TESTFN, 'dir')) - os.mkdir(path, mode=0o700) - out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem") - os.rmdir(path) - out = out.strip().rsplit(" ", 1)[1] - self.assertEqual( - out, - '"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"', - ) - - 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.assertHasAttr(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", "fstat", "fstatvfs", "tcgetpgrp", "ttyname"] - singles_fildes = {"fchdir"} - # systemd-nspawn --suppress-sync=true does not verify fd passed - # fdatasync() and fsync(), and always returns success - if not support.in_systemd_nspawn_sync_suppressed(): - singles += ["fdatasync", "fsync"] - singles_fildes |= {"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()') - 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()') - def test_fpathconf(self): - self.assertIn("PC_NAME_MAX", os.pathconf_names) - self.check_bool(os.pathconf, "PC_NAME_MAX") - self.check_bool(os.fpathconf, "PC_NAME_MAX") - - @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') - @unittest.skipIf( - support.linked_to_musl(), - 'musl pathconf ignores the file descriptor and returns a constant', - ) - def test_fpathconf_bad_fd(self): - self.check(os.pathconf, "PC_NAME_MAX") - self.check(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, 'readinto'), 'test needs os.readinto()') - def test_readinto(self): - self.check(os.readinto, bytearray(5)) - - @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') - # Ruff false positive -- it thinks we're redefining `ctypes` here - import ctypes.wintypes # noqa: F811 - - 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 to the owner (us) - subprocess.check_output([ICACLS, filename, "/grant", "*WD:(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)') - 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()) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - 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 - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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, '') - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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, '', {}) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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, ['']) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @requires_os_func('spawnve') - def test_spawnve_invalid_env(self): - self._test_invalid_env(os.spawnve) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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_invalid_count(self): - with self.assertRaises(ValueError, msg="count cannot be negative"): - await self.sendfile_wrapper(self.sockno, self.fileno, offset=0, - count=-1) - - 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"*256, **kwargs) - self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*256) - removexattr(fn, s("user.test"), **kwargs) - many = sorted("user.test{}".format(i) for i in range(32)) - 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: - known_errnos = [errno.EINVAL, errno.ENOTTY] - if sys.platform == "android": - # The Android testbed redirects the native stdout to a pipe, - # which returns a different error code. - known_errnos.append(errno.EACCES) - if sys.platform == "win32" or e.errno in known_errnos: - # 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') -@unittest.skipIf(sys.platform == "android", "gh-124873: Test is flaky on Android") -@support.requires_linux_version(2, 6, 30) -class TimerfdTests(unittest.TestCase): - # gh-126112: Use 10 ms to tolerate slow buildbots - CLOCK_RES_PLACES = 2 # 10 ms - CLOCK_RES = 10 ** -CLOCK_RES_PLACES - CLOCK_RES_NS = 10 ** (9 - CLOCK_RES_PLACES) - - 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=self.CLOCK_RES_PLACES) - self.assertAlmostEqual(next_expiration, 0.0, places=self.CLOCK_RES_PLACES) - - # 2nd call - next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval) - self.assertAlmostEqual(interval2, interval, places=self.CLOCK_RES_PLACES) - self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) - - # timerfd_gettime - next_expiration, interval2 = os.timerfd_gettime(fd) - self.assertAlmostEqual(interval2, interval, places=self.CLOCK_RES_PLACES) - self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) - - 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) - - @unittest.skipIf(sys.platform.startswith('netbsd'), - "gh-131263: Skip on NetBSD due to system freeze " - "with negative timer values") - 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=self.CLOCK_RES_PLACES) - self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) - - 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=self.CLOCK_RES_PLACES) - self.assertAlmostEqual(next_expiration, offset, places=self.CLOCK_RES_PLACES) - - 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)) - - @warnings_helper.ignore_fork_in_thread_deprecation_warnings() - @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), "") - - 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.assertIsSubclass(FakePath, os.PathLike) - self.assertIsInstance(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.assertNotIsSubclass(FakePath, A) - self.assertIsSubclass(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.assertNotHasAttr(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.") - @unittest.skipIf(_testcapi is None, "requires _testcapi") - 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_finalization(self): - code = """if 1: - import atexit - import os - - class AtFinalization: - def __del__(self): - print("OK") - pid = os.fork() - if pid != 0: - print("shouldn't be printed") - at_finalization = AtFinalization() - """ - _, out, err = assert_python_ok("-c", code) - self.assertEqual(b"OK\n", 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() diff --git a/Lib/test/test_os/__init__.py b/Lib/test/test_os/__init__.py new file mode 100644 index 00000000000000..bc502ef32d2916 --- /dev/null +++ b/Lib/test/test_os/__init__.py @@ -0,0 +1,6 @@ +import os.path +from test.support import load_package_tests + + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_os/test_dirfd.py b/Lib/test/test_os/test_dirfd.py new file mode 100644 index 00000000000000..739bab79d910f4 --- /dev/null +++ b/Lib/test/test_os/test_dirfd.py @@ -0,0 +1,228 @@ +""" +Tests for the posix *at functions. +""" + +import errno +import os +import stat +import time +import unittest +from contextlib import contextmanager +from test import support +from test.support import os_helper + +try: + import posix +except ImportError: + import nt as posix + + +class TestPosixDirFd(unittest.TestCase): + count = 0 + + @contextmanager + def prepare(self): + TestPosixDirFd.count += 1 + name = f'{os_helper.TESTFN}_{self.count}' + base_dir = f'{os_helper.TESTFN}_{self.count}base' + posix.mkdir(base_dir) + self.addCleanup(posix.rmdir, base_dir) + fullname = os.path.join(base_dir, name) + assert not os.path.exists(fullname) + with os_helper.open_dir_fd(base_dir) as dir_fd: + yield (dir_fd, name, fullname) + + @contextmanager + def prepare_file(self): + with self.prepare() as (dir_fd, name, fullname): + os_helper.create_empty_file(fullname) + self.addCleanup(posix.unlink, fullname) + yield (dir_fd, name, fullname) + + @unittest.skipUnless(os.access in os.supports_dir_fd, "test needs dir_fd support for os.access()") + def test_access_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname): + self.assertTrue(posix.access(name, os.R_OK, dir_fd=dir_fd)) + + @unittest.skipUnless(os.chmod in os.supports_dir_fd, "test needs dir_fd support in os.chmod()") + def test_chmod_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname): + posix.chmod(fullname, stat.S_IRUSR) + posix.chmod(name, stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd) + s = posix.stat(fullname) + self.assertEqual(s.st_mode & stat.S_IRWXU, + stat.S_IRUSR | stat.S_IWUSR) + + @unittest.skipUnless(hasattr(os, 'chown') and (os.chown in os.supports_dir_fd), + "test needs dir_fd support in os.chown()") + @unittest.skipIf(support.is_emscripten, "getgid() is a stub") + def test_chown_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname): + posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) + + @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") + def test_stat_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + with open(fullname, 'w') as outfile: + outfile.write("testline\n") + self.addCleanup(posix.unlink, fullname) + + s1 = posix.stat(fullname) + s2 = posix.stat(name, dir_fd=dir_fd) + self.assertEqual(s1, s2) + s2 = posix.stat(fullname, dir_fd=None) + self.assertEqual(s1, s2) + + self.assertRaisesRegex(TypeError, 'should be integer or None, not', + posix.stat, name, dir_fd=posix.getcwd()) + self.assertRaisesRegex(TypeError, 'should be integer or None, not', + posix.stat, name, dir_fd=float(dir_fd)) + self.assertRaises(OverflowError, + posix.stat, name, dir_fd=10**20) + + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + with self.assertRaises(OSError): + posix.stat('nonexisting', dir_fd=fd) + self.assertEqual(cm.filename, __file__) + + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") + def test_utime_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname): + now = time.time() + posix.utime(name, None, dir_fd=dir_fd) + posix.utime(name, dir_fd=dir_fd) + self.assertRaises(TypeError, posix.utime, name, + now, dir_fd=dir_fd) + self.assertRaises(TypeError, posix.utime, name, + (None, None), dir_fd=dir_fd) + self.assertRaises(TypeError, posix.utime, name, + (now, None), dir_fd=dir_fd) + self.assertRaises(TypeError, posix.utime, name, + (None, now), dir_fd=dir_fd) + self.assertRaises(TypeError, posix.utime, name, + (now, "x"), dir_fd=dir_fd) + posix.utime(name, (int(now), int(now)), dir_fd=dir_fd) + posix.utime(name, (now, now), dir_fd=dir_fd) + posix.utime(name, + (int(now), int((now - int(now)) * 1e9)), dir_fd=dir_fd) + posix.utime(name, dir_fd=dir_fd, + times=(int(now), int((now - int(now)) * 1e9))) + + # try dir_fd and follow_symlinks together + if os.utime in os.supports_follow_symlinks: + try: + posix.utime(name, follow_symlinks=False, dir_fd=dir_fd) + except ValueError: + # whoops! using both together not supported on this platform. + pass + + @unittest.skipIf( + support.is_wasi, + "WASI: symlink following on path_link is not supported" + ) + @unittest.skipUnless( + hasattr(os, "link") and os.link in os.supports_dir_fd, + "test needs dir_fd support in os.link()" + ) + def test_link_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname), \ + self.prepare() as (dir_fd2, linkname, fulllinkname): + try: + posix.link(name, linkname, src_dir_fd=dir_fd, dst_dir_fd=dir_fd2) + except PermissionError as e: + self.skipTest('posix.link(): %s' % e) + self.addCleanup(posix.unlink, fulllinkname) + # should have same inodes + self.assertEqual(posix.stat(fullname)[1], + posix.stat(fulllinkname)[1]) + + @unittest.skipUnless(os.mkdir in os.supports_dir_fd, "test needs dir_fd support in os.mkdir()") + def test_mkdir_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + posix.mkdir(name, dir_fd=dir_fd) + self.addCleanup(posix.rmdir, fullname) + posix.stat(fullname) # should not raise exception + + @unittest.skipUnless(hasattr(os, 'mknod') + and (os.mknod in os.supports_dir_fd) + and hasattr(stat, 'S_IFIFO'), + "test requires both stat.S_IFIFO and dir_fd support for os.mknod()") + def test_mknod_dir_fd(self): + # Test using mknodat() to create a FIFO (the only use specified + # by POSIX). + with self.prepare() as (dir_fd, name, fullname): + mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR + try: + posix.mknod(name, mode, 0, dir_fd=dir_fd) + except OSError as e: + # Some old systems don't allow unprivileged users to use + # mknod(), or only support creating device nodes. + self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) + else: + self.addCleanup(posix.unlink, fullname) + self.assertTrue(stat.S_ISFIFO(posix.stat(fullname).st_mode)) + + @unittest.skipUnless(os.open in os.supports_dir_fd, "test needs dir_fd support in os.open()") + def test_open_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + with open(fullname, 'wb') as outfile: + outfile.write(b"testline\n") + self.addCleanup(posix.unlink, fullname) + fd = posix.open(name, posix.O_RDONLY, dir_fd=dir_fd) + try: + res = posix.read(fd, 9) + self.assertEqual(b"testline\n", res) + finally: + posix.close(fd) + + @unittest.skipUnless(hasattr(os, 'readlink') and (os.readlink in os.supports_dir_fd), + "test needs dir_fd support in os.readlink()") + def test_readlink_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + os.symlink('symlink', fullname) + self.addCleanup(posix.unlink, fullname) + self.assertEqual(posix.readlink(name, dir_fd=dir_fd), 'symlink') + + @unittest.skipUnless(os.rename in os.supports_dir_fd, "test needs dir_fd support in os.rename()") + def test_rename_dir_fd(self): + with self.prepare_file() as (dir_fd, name, fullname), \ + self.prepare() as (dir_fd2, name2, fullname2): + posix.rename(name, name2, + src_dir_fd=dir_fd, dst_dir_fd=dir_fd2) + posix.stat(fullname2) # should not raise exception + posix.rename(fullname2, fullname) + + @unittest.skipUnless(os.symlink in os.supports_dir_fd, "test needs dir_fd support in os.symlink()") + def test_symlink_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + posix.symlink('symlink', name, dir_fd=dir_fd) + self.addCleanup(posix.unlink, fullname) + self.assertEqual(posix.readlink(fullname), 'symlink') + + @unittest.skipUnless(os.unlink in os.supports_dir_fd, "test needs dir_fd support in os.unlink()") + def test_unlink_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + os_helper.create_empty_file(fullname) + posix.stat(fullname) # should not raise exception + try: + posix.unlink(name, dir_fd=dir_fd) + self.assertRaises(OSError, posix.stat, fullname) + except: + self.addCleanup(posix.unlink, fullname) + raise + + @unittest.skipUnless(hasattr(os, 'mkfifo') and os.mkfifo in os.supports_dir_fd, "test needs dir_fd support in os.mkfifo()") + def test_mkfifo_dir_fd(self): + with self.prepare() as (dir_fd, name, fullname): + try: + posix.mkfifo(name, stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd) + except PermissionError as e: + self.skipTest('posix.mkfifo(): %s' % e) + self.addCleanup(posix.unlink, fullname) + self.assertTrue(stat.S_ISFIFO(posix.stat(fullname).st_mode)) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_dirs.py b/Lib/test/test_os/test_dirs.py new file mode 100644 index 00000000000000..a271c61e977680 --- /dev/null +++ b/Lib/test/test_os/test_dirs.py @@ -0,0 +1,766 @@ +""" +Test directories: listdir(), walk(), getcwd(), mkdir(), rmdir(), etc. +""" + +import errno +import itertools +import os +import shutil +import stat +import subprocess +import sys +import tempfile +import unittest +from test import support +from test.support import os_helper +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + + +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"]) + + 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(os_helper.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 support.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_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) + + @unittest.skipIf( + support.is_android, "dup return value is unpredictable on Android" + ) + def test_fd_finalization(self): + # Check that close()ing the fwalk() generator closes FDs + def getfd(): + fd = os.dup(1) + os.close(fd) + return fd + for topdown in (False, True): + old_fd = getfd() + it = self.fwalk(os_helper.TESTFN, topdown=topdown) + self.assertEqual(getfd(), old_fd) + next(it) + self.assertGreater(getfd(), old_fd) + it.close() + self.assertEqual(getfd(), old_fd) + + # fwalk() keeps file descriptors open + test_walk_many_open_files = 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 PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + def test_listdir(self): + self.assertIn(os_helper.TESTFN, posix.listdir(os.curdir)) + + def test_listdir_default(self): + # When listdir is called without argument, + # it's the same as listdir(os.curdir). + self.assertIn(os_helper.TESTFN, posix.listdir()) + + def test_listdir_bytes(self): + # When listdir is called with a bytes object, + # the returned strings are of type bytes. + self.assertIn(os.fsencode(os_helper.TESTFN), posix.listdir(b'.')) + + def test_listdir_bytes_like(self): + for cls in bytearray, memoryview: + with self.assertRaises(TypeError): + posix.listdir(cls(b'.')) + + @unittest.skipUnless(posix.listdir in os.supports_fd, + "test needs fd support for posix.listdir()") + def test_listdir_fd(self): + f = posix.open(posix.getcwd(), posix.O_RDONLY) + self.addCleanup(posix.close, f) + self.assertEqual( + sorted(posix.listdir('.')), + sorted(posix.listdir(f)) + ) + # Check that the fd offset was reset (issue #13739) + self.assertEqual( + sorted(posix.listdir('.')), + sorted(posix.listdir(f)) + ) + + @unittest.skipUnless(hasattr(posix, 'chdir'), 'test needs posix.chdir()') + def test_chdir(self): + posix.chdir(os.curdir) + self.assertRaises(OSError, posix.chdir, os_helper.TESTFN) + + +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)}") + + @unittest.skipUnless(hasattr(posix, 'getcwd'), 'test needs posix.getcwd()') + def test_getcwd_long_pathnames(self): + dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' + curdir = os.getcwd() + base_path = os.path.abspath(os_helper.TESTFN) + '.getcwd' + + try: + os.mkdir(base_path) + os.chdir(base_path) + except: + # Just returning nothing instead of the SkipTest exception, because + # the test results in Error in that case. Is that ok? + # raise unittest.SkipTest("cannot create directory for testing") + return + + def _create_and_do_getcwd(dirname, current_path_length = 0): + try: + os.mkdir(dirname) + except: + raise unittest.SkipTest("mkdir cannot create directory sufficiently deep for getcwd test") + + os.chdir(dirname) + try: + os.getcwd() + if current_path_length < 1027: + _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) + finally: + os.chdir('..') + os.rmdir(dirname) + + _create_and_do_getcwd(dirname) + + finally: + os.chdir(curdir) + os_helper.rmtree(base_path) + + def test_getcwdb(self): + cwd = os.getcwdb() + self.assertIsInstance(cwd, bytes) + self.assertEqual(os.fsdecode(cwd), os.getcwd()) + + +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_wasi, + "WASI's umask is a stub." + ) + def test_mode(self): + # Note: in some cases, the umask might already be 2 in which case this + # will pass even if os.umask is actually broken. + 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_wasi, + "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_wasi, + "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) + + @unittest.skipUnless(os.name == 'nt', "requires Windows") + def test_win32_mkdir_700(self): + base = os_helper.TESTFN + path = os.path.abspath(os.path.join(os_helper.TESTFN, 'dir')) + os.mkdir(path, mode=0o700) + out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem") + os.rmdir(path) + out = out.strip().rsplit(" ", 1)[1] + self.assertEqual( + out, + '"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"', + ) + + 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) + + +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)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_encoding.py b/Lib/test/test_os/test_encoding.py new file mode 100644 index 00000000000000..962e9af916bf3f --- /dev/null +++ b/Lib/test/test_os/test_encoding.py @@ -0,0 +1,121 @@ +""" +Test encodings: filename encoding, os.fsencode(), device_encoding(), etc. +""" + +import codecs +import locale +import os +import shutil +import sys +import unittest +from platform import win32_is_iot +from test.support import os_helper + + +@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)) + + +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)') + def test_device_encoding(self): + encoding = os.device_encoding(0) + self.assertIsNotNone(encoding) + self.assertTrue(codecs.lookup(encoding)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_environ.py b/Lib/test/test_os/test_environ.py new file mode 100644 index 00000000000000..d11e15faf8820c --- /dev/null +++ b/Lib/test/test_os/test_environ.py @@ -0,0 +1,401 @@ +""" +Test the environment: os.environ, putenv(), etc. +""" + +import os +import subprocess +import sys +import unittest +import warnings +from test import support +from test import mapping_tests +from test.support import os_helper +from test.support import unix_shell + +try: + import posix +except ImportError: + import nt as posix + + +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) + + def test_reload_environ(self): + # Test os.reload_environ() + has_environb = hasattr(os, 'environb') + + # Test with putenv() which doesn't update os.environ + os.environ['test_env'] = 'python_value' + os.putenv("test_env", "new_value") + self.assertEqual(os.environ['test_env'], 'python_value') + if has_environb: + self.assertEqual(os.environb[b'test_env'], b'python_value') + + os.reload_environ() + self.assertEqual(os.environ['test_env'], 'new_value') + if has_environb: + self.assertEqual(os.environb[b'test_env'], b'new_value') + + # Test with unsetenv() which doesn't update os.environ + os.unsetenv('test_env') + self.assertEqual(os.environ['test_env'], 'new_value') + if has_environb: + self.assertEqual(os.environb[b'test_env'], b'new_value') + + os.reload_environ() + self.assertNotIn('test_env', os.environ) + if has_environb: + self.assertNotIn(b'test_env', os.environb) + + if has_environb: + # test reload_environ() on os.environb with putenv() + os.environb[b'test_env'] = b'python_value2' + os.putenv("test_env", "new_value2") + self.assertEqual(os.environb[b'test_env'], b'python_value2') + self.assertEqual(os.environ['test_env'], 'python_value2') + + os.reload_environ() + self.assertEqual(os.environb[b'test_env'], b'new_value2') + self.assertEqual(os.environ['test_env'], 'new_value2') + + # test reload_environ() on os.environb with unsetenv() + os.unsetenv('test_env') + self.assertEqual(os.environb[b'test_env'], b'new_value2') + self.assertEqual(os.environ['test_env'], 'new_value2') + + os.reload_environ() + self.assertNotIn(b'test_env', os.environb) + self.assertNotIn('test_env', os.environ) + + +class PosixTester(unittest.TestCase): + + def test_environ(self): + if os.name == "nt": + item_type = str + else: + item_type = bytes + for k, v in posix.environ.items(): + self.assertEqual(type(k), item_type) + self.assertEqual(type(v), item_type) + + def test_putenv(self): + with self.assertRaises(ValueError): + os.putenv('FRUIT\0VEGETABLE', 'cabbage') + with self.assertRaises(ValueError): + os.putenv('FRUIT', 'orange\0VEGETABLE=cabbage') + with self.assertRaises(ValueError): + os.putenv('FRUIT=ORANGE', 'lemon') + if os.name == 'posix': + with self.assertRaises(ValueError): + os.putenv(b'FRUIT\0VEGETABLE', b'cabbage') + with self.assertRaises(ValueError): + os.putenv(b'FRUIT', b'orange\0VEGETABLE=cabbage') + with self.assertRaises(ValueError): + os.putenv(b'FRUIT=ORANGE', b'lemon') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_fd.py b/Lib/test/test_os/test_fd.py new file mode 100644 index 00000000000000..04ba340a3747c4 --- /dev/null +++ b/Lib/test/test_os/test_fd.py @@ -0,0 +1,337 @@ +""" +Test file descriptors: pipe(), set_blocking(), memfd_create(), eventfd, etc. +""" + +import errno +import itertools +import os +import select +import struct +import sys +import unittest +import warnings +from test import support +from test.support import os_helper + +try: + import posix +except ImportError: + import nt as posix + + +@unittest.skipIf(support.is_wasi, "Cannot create invalid FD on WASI.") +class TestInvalidFD(unittest.TestCase): + singles = ["fchdir", "dup", "fstat", "fstatvfs", "tcgetpgrp", "ttyname"] + singles_fildes = {"fchdir"} + # systemd-nspawn --suppress-sync=true does not verify fd passed + # fdatasync() and fsync(), and always returns success + if not support.in_systemd_nspawn_sync_suppressed(): + singles += ["fdatasync", "fsync"] + singles_fildes |= {"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()') + 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()') + def test_fpathconf(self): + self.assertIn("PC_NAME_MAX", os.pathconf_names) + self.check_bool(os.pathconf, "PC_NAME_MAX") + self.check_bool(os.fpathconf, "PC_NAME_MAX") + + @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') + @unittest.skipIf( + support.linked_to_musl(), + 'musl pathconf ignores the file descriptor and returns a constant', + ) + def test_fpathconf_bad_fd(self): + self.check(os.pathconf, "PC_NAME_MAX") + self.check(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, 'readinto'), 'test needs os.readinto()') + def test_readinto(self): + self.check(os.readinto, bytearray(5)) + + @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, '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, '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 PosixTester(unittest.TestCase): + + @unittest.skipUnless(hasattr(posix, 'pipe'), 'test needs posix.pipe()') + def test_pipe(self): + reader, writer = posix.pipe() + os.close(reader) + os.close(writer) + + @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") + @support.requires_linux_version(2, 6, 27) + def test_pipe2(self): + self.assertRaises(TypeError, os.pipe2, 'DEADBEEF') + self.assertRaises(TypeError, os.pipe2, 0, 0) + + # try calling with flags = 0, like os.pipe() + r, w = os.pipe2(0) + os.close(r) + os.close(w) + + # test flags + r, w = os.pipe2(os.O_CLOEXEC|os.O_NONBLOCK) + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + self.assertFalse(os.get_inheritable(r)) + self.assertFalse(os.get_inheritable(w)) + self.assertFalse(os.get_blocking(r)) + self.assertFalse(os.get_blocking(w)) + # try reading from an empty pipe: this should fail, not block + self.assertRaises(OSError, os.read, r, 1) + # try a write big enough to fill-up the pipe: this should either + # fail or perform a partial write, not block + try: + os.write(w, b'x' * support.PIPE_MAX_SIZE) + except OSError: + pass + + @support.cpython_only + @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") + @support.requires_linux_version(2, 6, 27) + def test_pipe2_c_limits(self): + # Issue 15989 + import _testcapi + self.assertRaises(OverflowError, os.pipe2, _testcapi.INT_MAX + 1) + self.assertRaises(OverflowError, os.pipe2, _testcapi.UINT_MAX + 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_file.py b/Lib/test/test_os/test_file.py new file mode 100644 index 00000000000000..d1f2a04957bf53 --- /dev/null +++ b/Lib/test/test_os/test_file.py @@ -0,0 +1,954 @@ +""" +Test files: open(), read(), pwritev(), truncate(), etc. +""" + +import errno +import os +import sys +import unittest +from test import support +from test.support import os_helper +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + +try: + import fcntl +except ImportError: + fcntl = None + +try: + from _testcapi import INT_MAX, PY_SSIZE_T_MAX +except ImportError: + INT_MAX = PY_SSIZE_T_MAX = sys.maxsize + + +# 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') + +requires_32b = unittest.skipUnless( + # Emscripten/WASI have 32 bits pointers, but support 64 bits syscall args. + sys.maxsize < 2**32 and not (support.is_emscripten or support.is_wasi), + 'test is only meaningful on 32-bit builds' +) + + +# 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_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") + + def test_readinto(self): + with open(os_helper.TESTFN, "w+b") as fobj: + fobj.write(b"spam") + fobj.flush() + fd = fobj.fileno() + os.lseek(fd, 0, 0) + # Oversized so readinto without hitting end. + buffer = bytearray(7) + s = os.readinto(fd, buffer) + self.assertEqual(type(s), int) + self.assertEqual(s, 4) + # Should overwrite the first 4 bytes of the buffer. + self.assertEqual(buffer[:4], b"spam") + + # Readinto at EOF should return 0 and not touch buffer. + buffer[:] = b"notspam" + s = os.readinto(fd, buffer) + self.assertEqual(type(s), int) + self.assertEqual(s, 0) + self.assertEqual(bytes(buffer), b"notspam") + s = os.readinto(fd, buffer) + self.assertEqual(s, 0) + self.assertEqual(bytes(buffer), b"notspam") + + # Readinto a 0 length bytearray when at EOF should return 0 + self.assertEqual(os.readinto(fd, bytearray()), 0) + + # Readinto a 0 length bytearray with data available should return 0. + os.lseek(fd, 0, 0) + self.assertEqual(os.readinto(fd, bytearray()), 0) + + @unittest.skipUnless(hasattr(os, 'get_blocking'), + 'needs os.get_blocking() and os.set_blocking()') + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") + @unittest.skipIf(support.is_emscripten, "set_blocking does not work correctly") + def test_readinto_non_blocking(self): + # Verify behavior of a readinto which would block on a non-blocking fd. + r, w = os.pipe() + try: + os.set_blocking(r, False) + with self.assertRaises(BlockingIOError): + os.readinto(r, bytearray(5)) + + # Pass some data through + os.write(w, b"spam") + self.assertEqual(os.readinto(r, bytearray(4)), 4) + + # Still don't block or return 0. + with self.assertRaises(BlockingIOError): + os.readinto(r, bytearray(5)) + + # At EOF should return size 0 + os.close(w) + w = None + self.assertEqual(os.readinto(r, bytearray(5)), 0) + self.assertEqual(os.readinto(r, bytearray(5)), 0) # Still EOF + + finally: + os.close(r) + if w is not None: + os.close(w) + + def test_readinto_badarg(self): + with open(os_helper.TESTFN, "w+b") as fobj: + fobj.write(b"spam") + fobj.flush() + fd = fobj.fileno() + os.lseek(fd, 0, 0) + + for bad_arg in ("test", bytes(), 14): + with self.subTest(f"bad buffer {type(bad_arg)}"): + with self.assertRaises(TypeError): + os.readinto(fd, bad_arg) + + with self.subTest("doesn't work on file objects"): + with self.assertRaises(TypeError): + os.readinto(fobj, bytearray(5)) + + # takes two args + with self.assertRaises(TypeError): + os.readinto(fd) + + # No data should have been read with the bad arguments. + buffer = bytearray(4) + s = os.readinto(fd, buffer) + self.assertEqual(s, 4) + self.assertEqual(buffer, 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') + + + @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_readinto(self, size): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'test') + + # Issue #21932: For readinto the buffer contains the length rather than + # a length being passed explicitly to read, should still get capped to a + # valid size / not raise an OverflowError for sizes larger than INT_MAX. + buffer = bytearray(INT_MAX + 10) + with open(os_helper.TESTFN, "rb") as fp: + length = os.readinto(fp.fileno(), buffer) + + # 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(length, 4) + self.assertEqual(buffer[:4], 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 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]) + + +class PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + @unittest.skipUnless(hasattr(posix, 'ftruncate'), + 'test needs posix.ftruncate()') + def test_ftruncate(self): + fp = open(os_helper.TESTFN, 'w+') + try: + # we need to have some data to truncate + fp.write('test') + fp.flush() + posix.ftruncate(fp.fileno(), 0) + finally: + fp.close() + + @unittest.skipUnless(hasattr(posix, 'truncate'), "test needs posix.truncate()") + def test_truncate(self): + with open(os_helper.TESTFN, 'w') as fp: + fp.write('test') + fp.flush() + posix.truncate(os_helper.TESTFN, 0) + + @unittest.skipUnless(hasattr(posix, 'pread'), "test needs posix.pread()") + def test_pread(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b'test') + os.lseek(fd, 0, os.SEEK_SET) + self.assertEqual(b'es', posix.pread(fd, 2, 1)) + # the first pread() shouldn't disturb the file offset + self.assertEqual(b'te', posix.read(fd, 2)) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") + def test_preadv(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b'test1tt2t3t5t6t6t8') + buf = [bytearray(i) for i in [5, 3, 2]] + self.assertEqual(posix.preadv(fd, buf, 3), 10) + self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf)) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") + @unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI") + def test_preadv_flags(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b'test1tt2t3t5t6t6t8') + buf = [bytearray(i) for i in [5, 3, 2]] + self.assertEqual(posix.preadv(fd, buf, 3, os.RWF_HIPRI), 10) + self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf)) + except NotImplementedError: + self.skipTest("preadv2 not available") + except OSError as inst: + # Is possible that the macro RWF_HIPRI was defined at compilation time + # but the option is not supported by the kernel or the runtime libc shared + # library. + if inst.errno in {errno.EINVAL, errno.ENOTSUP}: + raise unittest.SkipTest("RWF_HIPRI is not supported by the current system") + else: + raise + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") + @requires_32b + def test_preadv_overflow_32bits(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + buf = [bytearray(2**16)] * 2**15 + with self.assertRaises(OSError) as cm: + os.preadv(fd, buf, 0) + self.assertEqual(cm.exception.errno, errno.EINVAL) + self.assertEqual(bytes(buf[0]), b'\0'* 2**16) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()") + def test_pwrite(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b'test') + os.lseek(fd, 0, os.SEEK_SET) + posix.pwrite(fd, b'xx', 1) + self.assertEqual(b'txxt', posix.read(fd, 4)) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") + def test_pwritev(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b"xx") + os.lseek(fd, 0, os.SEEK_SET) + n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2) + self.assertEqual(n, 10) + + os.lseek(fd, 0, os.SEEK_SET) + self.assertEqual(b'xxtest1tt2t3', posix.read(fd, 100)) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") + @unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC") + def test_pwritev_flags(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd,b"xx") + os.lseek(fd, 0, os.SEEK_SET) + n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2, os.RWF_SYNC) + self.assertEqual(n, 10) + + os.lseek(fd, 0, os.SEEK_SET) + self.assertEqual(b'xxtest1tt2', posix.read(fd, 100)) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") + @requires_32b + def test_pwritev_overflow_32bits(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + with self.assertRaises(OSError) as cm: + os.pwritev(fd, [b"x" * 2**16] * 2**15, 0) + self.assertEqual(cm.exception.errno, errno.EINVAL) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'posix_fallocate'), + "test needs posix.posix_fallocate()") + def test_posix_fallocate(self): + fd = os.open(os_helper.TESTFN, os.O_WRONLY | os.O_CREAT) + try: + posix.posix_fallocate(fd, 0, 10) + except OSError as inst: + # issue10812, ZFS doesn't appear to support posix_fallocate, + # so skip Solaris-based since they are likely to have ZFS. + # issue33655: Also ignore EINVAL on *BSD since ZFS is also + # often used there. + if inst.errno == errno.EINVAL and sys.platform.startswith( + ('sunos', 'freebsd', 'openbsd', 'gnukfreebsd')): + raise unittest.SkipTest("test may fail on ZFS filesystems") + elif inst.errno == errno.EOPNOTSUPP and sys.platform.startswith("netbsd"): + raise unittest.SkipTest("test may fail on FFS filesystems") + else: + raise + finally: + os.close(fd) + + # issue31106 - posix_fallocate() does not set error in errno. + @unittest.skipUnless(hasattr(posix, 'posix_fallocate'), + "test needs posix.posix_fallocate()") + def test_posix_fallocate_errno(self): + try: + posix.posix_fallocate(-42, 0, 10) + except OSError as inst: + if inst.errno != errno.EBADF: + raise + + @unittest.skipUnless(hasattr(posix, 'posix_fadvise'), + "test needs posix.posix_fadvise()") + def test_posix_fadvise(self): + fd = os.open(os_helper.TESTFN, os.O_RDONLY) + try: + posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_WILLNEED) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'posix_fadvise'), + "test needs posix.posix_fadvise()") + def test_posix_fadvise_errno(self): + try: + posix.posix_fadvise(-42, 0, 0, posix.POSIX_FADV_WILLNEED) + except OSError as inst: + if inst.errno != errno.EBADF: + raise + + @unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()") + def test_writev(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + n = os.writev(fd, (b'test1', b'tt2', b't3')) + self.assertEqual(n, 10) + + os.lseek(fd, 0, os.SEEK_SET) + self.assertEqual(b'test1tt2t3', posix.read(fd, 10)) + + # Issue #20113: empty list of buffers should not crash + try: + size = posix.writev(fd, []) + except OSError: + # writev(fd, []) raises OSError(22, "Invalid argument") + # on OpenIndiana + pass + else: + self.assertEqual(size, 0) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()") + @requires_32b + def test_writev_overflow_32bits(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + with self.assertRaises(OSError) as cm: + os.writev(fd, [b"x" * 2**16] * 2**15) + self.assertEqual(cm.exception.errno, errno.EINVAL) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()") + def test_readv(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + os.write(fd, b'test1tt2t3') + os.lseek(fd, 0, os.SEEK_SET) + buf = [bytearray(i) for i in [5, 3, 2]] + self.assertEqual(posix.readv(fd, buf), 10) + self.assertEqual([b'test1', b'tt2', b't3'], [bytes(i) for i in buf]) + + # Issue #20113: empty list of buffers should not crash + try: + size = posix.readv(fd, []) + except OSError: + # readv(fd, []) raises OSError(22, "Invalid argument") + # on OpenIndiana + pass + else: + self.assertEqual(size, 0) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()") + @requires_32b + def test_readv_overflow_32bits(self): + fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) + try: + buf = [bytearray(2**16)] * 2**15 + with self.assertRaises(OSError) as cm: + os.readv(fd, buf) + self.assertEqual(cm.exception.errno, errno.EINVAL) + self.assertEqual(bytes(buf[0]), b'\0'* 2**16) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'dup'), + 'test needs posix.dup()') + @unittest.skipIf(support.is_wasi, "WASI does not have dup()") + def test_dup(self): + fp = open(os_helper.TESTFN) + try: + fd = posix.dup(fp.fileno()) + self.assertIsInstance(fd, int) + os.close(fd) + finally: + fp.close() + + @unittest.skipUnless(hasattr(posix, 'dup2'), + 'test needs posix.dup2()') + @unittest.skipIf(support.is_wasi, "WASI does not have dup2()") + def test_dup2(self): + fp1 = open(os_helper.TESTFN) + fp2 = open(os_helper.TESTFN) + try: + posix.dup2(fp1.fileno(), fp2.fileno()) + finally: + fp1.close() + fp2.close() + + @unittest.skipUnless(hasattr(os, 'O_CLOEXEC'), "needs os.O_CLOEXEC") + @support.requires_linux_version(2, 6, 23) + @support.requires_subprocess() + def test_oscloexec(self): + fd = os.open(os_helper.TESTFN, os.O_RDONLY|os.O_CLOEXEC) + self.addCleanup(os.close, fd) + self.assertFalse(os.get_inheritable(fd)) + + @unittest.skipUnless(hasattr(posix, 'lockf'), "test needs posix.lockf()") + def test_lockf(self): + fd = os.open(os_helper.TESTFN, os.O_WRONLY | os.O_CREAT) + try: + os.write(fd, b'test') + os.lseek(fd, 0, os.SEEK_SET) + posix.lockf(fd, posix.F_LOCK, 4) + # section is locked + posix.lockf(fd, posix.F_ULOCK, 4) + finally: + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'), + 'test needs posix.O_EXLOCK') + def test_osexlock(self): + fd = os.open(os_helper.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, os_helper.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + if hasattr(posix, "O_SHLOCK"): + fd = os.open(os_helper.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, os_helper.TESTFN, + os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + @unittest.skipUnless(hasattr(posix, 'O_SHLOCK'), + 'test needs posix.O_SHLOCK') + def test_osshlock(self): + fd1 = os.open(os_helper.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + fd2 = os.open(os_helper.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + os.close(fd2) + os.close(fd1) + + if hasattr(posix, "O_EXLOCK"): + fd = os.open(os_helper.TESTFN, + os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) + self.assertRaises(OSError, os.open, os_helper.TESTFN, + os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) + os.close(fd) + + +# 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, 'SEEK_HOLE'), + "test needs an OS that reports file holes") + def test_fs_holes(self): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + + # Even if the filesystem doesn't report holes, + # if the OS supports it the SEEK_* constants + # will be defined and will have a consistent + # behaviour: + # os.SEEK_DATA = current position + # os.SEEK_HOLE = end of file position + with open(os_helper.TESTFN, 'w+b') as fp: + fp.write(b"hello") + fp.flush() + size = fp.tell() + fno = fp.fileno() + try : + for i in range(size): + self.assertEqual(i, os.lseek(fno, i, os.SEEK_DATA)) + self.assertLessEqual(size, os.lseek(fno, i, os.SEEK_HOLE)) + self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_DATA) + self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_HOLE) + except OSError : + # Some OSs claim to support SEEK_HOLE/SEEK_DATA + # but it is not true. + # For instance: + # http://lists.freebsd.org/pipermail/freebsd-amd64/2012-January/014332.html + raise unittest.SkipTest("OSError raised!") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_file_attrs.py b/Lib/test/test_os/test_file_attrs.py new file mode 100644 index 00000000000000..ae3f4db3717a4b --- /dev/null +++ b/Lib/test/test_os/test_file_attrs.py @@ -0,0 +1,518 @@ +""" +Test file attributes: chmod(), chown(), access(), setxattr(), chflags(), etc. +""" + +import decimal +import errno +import fractions +import os +import platform +import stat +import sys +import tempfile +import unittest +from test import support +from test.support import os_helper +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + +try: + import pwd + all_users = [u.pw_uid for u in pwd.getpwall()] +except (ImportError, AttributeError): + all_users = [] + + +_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(), + os_helper.TESTFN + '-dummy-symlink') + +root_in_posix = False +if hasattr(os, 'geteuid'): + root_in_posix = (os.geteuid() == 0) + + +@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 PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + def _test_all_chown_common(self, chown_func, first_param, stat_func): + """Common code for chown, fchown and lchown tests.""" + def check_stat(uid, gid): + if stat_func is not None: + stat = stat_func(first_param) + self.assertEqual(stat.st_uid, uid) + self.assertEqual(stat.st_gid, gid) + uid = os.getuid() + gid = os.getgid() + # test a successful chown call + chown_func(first_param, uid, gid) + check_stat(uid, gid) + chown_func(first_param, -1, gid) + check_stat(uid, gid) + chown_func(first_param, uid, -1) + check_stat(uid, gid) + + if sys.platform == "vxworks": + # On VxWorks, root user id is 1 and 0 means no login user: + # both are super users. + is_root = (uid in (0, 1)) + else: + is_root = (uid == 0) + if support.is_emscripten: + # Emscripten getuid() / geteuid() always return 0 (root), but + # cannot chown uid/gid to random value. + pass + elif is_root: + # Try an amusingly large uid/gid to make sure we handle + # large unsigned values. (chown lets you use any + # uid/gid you like, even if they aren't defined.) + # + # On VxWorks uid_t is defined as unsigned short. A big + # value greater than 65535 will result in underflow error. + # + # This problem keeps coming up: + # http://bugs.python.org/issue1747858 + # http://bugs.python.org/issue4591 + # http://bugs.python.org/issue15301 + # Hopefully the fix in 4591 fixes it for good! + # + # This part of the test only runs when run as root. + # Only scary people run their tests as root. + + big_value = (2**31 if sys.platform != "vxworks" else 2**15) + chown_func(first_param, big_value, big_value) + check_stat(big_value, big_value) + chown_func(first_param, -1, -1) + check_stat(big_value, big_value) + chown_func(first_param, uid, gid) + check_stat(uid, gid) + elif platform.system() in ('HP-UX', 'SunOS'): + # HP-UX and Solaris can allow a non-root user to chown() to root + # (issue #5113) + raise unittest.SkipTest("Skipping because of non-standard chown() " + "behavior") + else: + # non-root cannot chown to root, raises OSError + self.assertRaises(OSError, chown_func, first_param, 0, 0) + check_stat(uid, gid) + self.assertRaises(OSError, chown_func, first_param, 0, -1) + check_stat(uid, gid) + if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) + # test illegal types + for t in str, float: + self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) + check_stat(uid, gid) + self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) + check_stat(uid, gid) + + @unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") + @unittest.skipIf(support.is_emscripten, "getgid() is a stub") + def test_chown(self): + # raise an OSError if the file does not exist + os.unlink(os_helper.TESTFN) + self.assertRaises(OSError, posix.chown, os_helper.TESTFN, -1, -1) + + # re-create the file + os_helper.create_empty_file(os_helper.TESTFN) + self._test_all_chown_common(posix.chown, os_helper.TESTFN, posix.stat) + + @os_helper.skip_unless_working_chmod + @unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()") + @unittest.skipIf(support.is_emscripten, "getgid() is a stub") + def test_fchown(self): + os.unlink(os_helper.TESTFN) + + # re-create the file + test_file = open(os_helper.TESTFN, 'w') + try: + fd = test_file.fileno() + self._test_all_chown_common(posix.fchown, fd, + getattr(posix, 'fstat', None)) + finally: + test_file.close() + + @os_helper.skip_unless_working_chmod + @unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()") + def test_lchown(self): + os.unlink(os_helper.TESTFN) + # create a symlink + os.symlink(_DUMMY_SYMLINK, os_helper.TESTFN) + self._test_all_chown_common(posix.lchown, os_helper.TESTFN, + getattr(posix, 'lstat', None)) + + @unittest.skipUnless(hasattr(posix, 'access'), 'test needs posix.access()') + def test_access(self): + self.assertTrue(posix.access(os_helper.TESTFN, os.R_OK)) + + @unittest.skipUnless(hasattr(posix, 'umask'), 'test needs posix.umask()') + def test_umask(self): + old_mask = posix.umask(0) + self.assertIsInstance(old_mask, int) + posix.umask(old_mask) + + def check_chmod(self, chmod_func, target, **kwargs): + closefd = not isinstance(target, int) + mode = os.stat(target).st_mode + try: + new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + try: + with open(target, 'wb+', closefd=closefd): + pass + except PermissionError: + pass + new_mode = mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + with open(target, 'wb+', closefd=closefd): + pass + finally: + chmod_func(target, mode) + + @os_helper.skip_unless_working_chmod + def test_chmod_file(self): + self.check_chmod(posix.chmod, os_helper.TESTFN) + + def tempdir(self): + target = os_helper.TESTFN + 'd' + posix.mkdir(target) + self.addCleanup(posix.rmdir, target) + return target + + @os_helper.skip_unless_working_chmod + def test_chmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.chmod, target) + + @os_helper.skip_unless_working_chmod + def test_fchmod_file(self): + with open(os_helper.TESTFN, 'wb+') as f: + self.check_chmod(posix.fchmod, f.fileno()) + self.check_chmod(posix.chmod, f.fileno()) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_file(self): + self.check_chmod(posix.lchmod, os_helper.TESTFN) + self.check_chmod(posix.chmod, os_helper.TESTFN, follow_symlinks=False) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.lchmod, target) + self.check_chmod(posix.chmod, target, follow_symlinks=False) + + def check_chmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + try: + new_mode = target_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + new_mode = target_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + finally: + posix.chmod(target, target_mode) + + def check_lchmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + new_mode = link_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + new_mode = link_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + + @os_helper.skip_unless_symlink + def test_chmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @os_helper.skip_unless_symlink + def test_chmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link, target_is_directory=True) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + + def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): + st = os.stat(target_file) + self.assertHasAttr(st, 'st_flags') + + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + flags = st.st_flags | stat.UF_IMMUTABLE + try: + chflags_func(target_file, flags, **kwargs) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + + try: + new_st = os.stat(target_file) + self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags) + try: + fd = open(target_file, 'w+') + except OSError as e: + self.assertEqual(e.errno, errno.EPERM) + finally: + posix.chflags(target_file, st.st_flags) + + @unittest.skipUnless(hasattr(posix, 'chflags'), 'test needs os.chflags()') + def test_chflags(self): + self._test_chflags_regular_file(posix.chflags, os_helper.TESTFN) + + @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()') + def test_lchflags_regular_file(self): + self._test_chflags_regular_file(posix.lchflags, os_helper.TESTFN) + self._test_chflags_regular_file(posix.chflags, os_helper.TESTFN, + follow_symlinks=False) + + @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()') + def test_lchflags_symlink(self): + testfn_st = os.stat(os_helper.TESTFN) + + self.assertHasAttr(testfn_st, 'st_flags') + + self.addCleanup(os_helper.unlink, _DUMMY_SYMLINK) + os.symlink(os_helper.TESTFN, _DUMMY_SYMLINK) + dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) + + def chflags_nofollow(path, flags): + return posix.chflags(path, flags, follow_symlinks=False) + + for fn in (posix.lchflags, chflags_nofollow): + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + flags = dummy_symlink_st.st_flags | stat.UF_IMMUTABLE + try: + fn(_DUMMY_SYMLINK, flags) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + try: + new_testfn_st = os.stat(os_helper.TESTFN) + new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) + + self.assertEqual(testfn_st.st_flags, new_testfn_st.st_flags) + self.assertEqual(dummy_symlink_st.st_flags | stat.UF_IMMUTABLE, + new_dummy_symlink_st.st_flags) + finally: + fn(_DUMMY_SYMLINK, dummy_symlink_st.st_flags) + + +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"*256, **kwargs) + self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*256) + removexattr(fn, s("user.test"), **kwargs) + many = sorted("user.test{}".format(i) for i in range(32)) + 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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_fspath.py b/Lib/test/test_os/test_fspath.py new file mode 100644 index 00000000000000..50421172d007db --- /dev/null +++ b/Lib/test/test_os/test_fspath.py @@ -0,0 +1,121 @@ +import os +import types +import unittest +from test.support.os_helper import FakePath + + +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.assertIsSubclass(FakePath, os.PathLike) + self.assertIsInstance(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.assertNotIsSubclass(FakePath, A) + self.assertIsSubclass(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.assertNotHasAttr(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") + +# 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() diff --git a/Lib/test/test_os/test_link.py b/Lib/test/test_os/test_link.py new file mode 100644 index 00000000000000..9d681a6bb02b5f --- /dev/null +++ b/Lib/test/test_os/test_link.py @@ -0,0 +1,205 @@ +""" +Test symbolic and hard links: os.link(), os.symlink(), etc. +""" + +import os +import shutil +import sys +import unittest +from test.support import os_helper +from test.support.os_helper import FakePath +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + + +@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) + + +@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) + + +@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) + + +class PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + @os_helper.skip_unless_hardlink + @os_helper.skip_unless_symlink + def test_link_follow_symlinks(self): + default_follow = sys.platform.startswith( + ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5')) + default_no_follow = sys.platform.startswith(('win32', 'linux')) + orig = os_helper.TESTFN + symlink = orig + 'symlink' + posix.symlink(orig, symlink) + self.addCleanup(os_helper.unlink, symlink) + + with self.subTest('no follow_symlinks'): + # no follow_symlinks -> platform depending + link = orig + 'link' + posix.link(symlink, link) + self.addCleanup(os_helper.unlink, link) + if os.link in os.supports_follow_symlinks or default_follow: + self.assertEqual(posix.lstat(link), posix.lstat(orig)) + elif default_no_follow: + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) + + with self.subTest('follow_symlinks=False'): + # follow_symlinks=False -> duplicate the symlink itself + link = orig + 'link_nofollow' + try: + posix.link(symlink, link, follow_symlinks=False) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_no_follow: + raise + else: + self.addCleanup(os_helper.unlink, link) + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) + + with self.subTest('follow_symlinks=True'): + # follow_symlinks=True -> duplicate the target file + link = orig + 'link_following' + try: + posix.link(symlink, link, follow_symlinks=True) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_follow: + raise + else: + self.addCleanup(os_helper.unlink, link) + self.assertEqual(posix.lstat(link), posix.lstat(orig)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_macos_weaklink.py b/Lib/test/test_os/test_macos_weaklink.py new file mode 100644 index 00000000000000..5e69e69c31869f --- /dev/null +++ b/Lib/test/test_os/test_macos_weaklink.py @@ -0,0 +1,275 @@ +""" +Test macOS weak linking. +""" + +import sys +import unittest +if sys.platform != "darwin": + raise unittest.SkipTest("test weak linking on macOS") + +import os +from test.support import os_helper + +try: + import posix +except ImportError: + import nt as posix + + +class TestPosixWeaklinking(unittest.TestCase): + # These test cases verify that weak linking support on macOS works + # as expected. These cases only test new behaviour introduced by weak linking, + # regular behaviour is tested by the normal test cases. + # + # See the section on Weak Linking in Mac/README.txt for more information. + def setUp(self): + import sysconfig + import platform + + config_vars = sysconfig.get_config_vars() + self.available = { nm for nm in config_vars if nm.startswith("HAVE_") and config_vars[nm] } + self.mac_ver = tuple(int(part) for part in platform.mac_ver()[0].split(".")) + + def _verify_available(self, name): + if name not in self.available: + raise unittest.SkipTest(f"{name} not weak-linked") + + def test_pwritev(self): + self._verify_available("HAVE_PWRITEV") + if self.mac_ver >= (10, 16): + self.assertHasAttr(os, "pwritev") + self.assertHasAttr(os, "preadv") + + else: + self.assertNotHasAttr(os, "pwritev") + self.assertNotHasAttr(os, "preadv") + + def test_stat(self): + self._verify_available("HAVE_FSTATAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FSTATAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FSTATAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.stat("file", dir_fd=0) + + def test_ptsname_r(self): + self._verify_available("HAVE_PTSNAME_R") + if self.mac_ver >= (10, 13, 4): + self.assertIn("HAVE_PTSNAME_R", posix._have_functions) + else: + self.assertNotIn("HAVE_PTSNAME_R", posix._have_functions) + + def test_access(self): + self._verify_available("HAVE_FACCESSAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FACCESSAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FACCESSAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.access("file", os.R_OK, dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "follow_symlinks unavailable"): + os.access("file", os.R_OK, follow_symlinks=False) + + with self.assertRaisesRegex(NotImplementedError, "effective_ids unavailable"): + os.access("file", os.R_OK, effective_ids=True) + + def test_chmod(self): + self._verify_available("HAVE_FCHMODAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FCHMODAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FCHMODAT", posix._have_functions) + self.assertIn("HAVE_LCHMOD", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.chmod("file", 0o644, dir_fd=0) + + def test_chown(self): + self._verify_available("HAVE_FCHOWNAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FCHOWNAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FCHOWNAT", posix._have_functions) + self.assertIn("HAVE_LCHOWN", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.chown("file", 0, 0, dir_fd=0) + + def test_link(self): + self._verify_available("HAVE_LINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_LINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_LINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): + os.link("source", "target", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "dst_dir_fd unavailable"): + os.link("source", "target", dst_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): + os.link("source", "target", src_dir_fd=0, dst_dir_fd=0) + + # issue 41355: !HAVE_LINKAT code path ignores the follow_symlinks flag + with os_helper.temp_dir() as base_path: + link_path = os.path.join(base_path, "link") + target_path = os.path.join(base_path, "target") + source_path = os.path.join(base_path, "source") + + with open(source_path, "w") as fp: + fp.write("data") + + os.symlink("target", link_path) + + # Calling os.link should fail in the link(2) call, and + # should not reject *follow_symlinks* (to match the + # behaviour you'd get when building on a platform without + # linkat) + with self.assertRaises(FileExistsError): + os.link(source_path, link_path, follow_symlinks=True) + + with self.assertRaises(FileExistsError): + os.link(source_path, link_path, follow_symlinks=False) + + + def test_listdir_scandir(self): + self._verify_available("HAVE_FDOPENDIR") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FDOPENDIR", posix._have_functions) + + else: + self.assertNotIn("HAVE_FDOPENDIR", posix._have_functions) + + with self.assertRaisesRegex(TypeError, "listdir: path should be string, bytes, os.PathLike or None, not int"): + os.listdir(0) + + with self.assertRaisesRegex(TypeError, "scandir: path should be string, bytes, os.PathLike or None, not int"): + os.scandir(0) + + def test_mkdir(self): + self._verify_available("HAVE_MKDIRAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_MKDIRAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_MKDIRAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.mkdir("dir", dir_fd=0) + + def test_mkfifo(self): + self._verify_available("HAVE_MKFIFOAT") + if self.mac_ver >= (13, 0): + self.assertIn("HAVE_MKFIFOAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_MKFIFOAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.mkfifo("path", dir_fd=0) + + def test_mknod(self): + self._verify_available("HAVE_MKNODAT") + if self.mac_ver >= (13, 0): + self.assertIn("HAVE_MKNODAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_MKNODAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.mknod("path", dir_fd=0) + + def test_rename_replace(self): + self._verify_available("HAVE_RENAMEAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_RENAMEAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_RENAMEAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.rename("a", "b", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.rename("a", "b", dst_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.replace("a", "b", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.replace("a", "b", dst_dir_fd=0) + + def test_unlink_rmdir(self): + self._verify_available("HAVE_UNLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_UNLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_UNLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.unlink("path", dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.rmdir("path", dir_fd=0) + + def test_open(self): + self._verify_available("HAVE_OPENAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_OPENAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_OPENAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.open("path", os.O_RDONLY, dir_fd=0) + + def test_readlink(self): + self._verify_available("HAVE_READLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_READLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_READLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.readlink("path", dir_fd=0) + + def test_symlink(self): + self._verify_available("HAVE_SYMLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_SYMLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_SYMLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.symlink("a", "b", dir_fd=0) + + def test_utime(self): + self._verify_available("HAVE_FUTIMENS") + self._verify_available("HAVE_UTIMENSAT") + if self.mac_ver >= (10, 13): + self.assertIn("HAVE_FUTIMENS", posix._have_functions) + self.assertIn("HAVE_UTIMENSAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FUTIMENS", posix._have_functions) + self.assertNotIn("HAVE_UTIMENSAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.utime("path", dir_fd=0) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_misc.py b/Lib/test/test_os/test_misc.py new file mode 100644 index 00000000000000..379ebe2d432cd4 --- /dev/null +++ b/Lib/test/test_os/test_misc.py @@ -0,0 +1,497 @@ +import errno +import os +import signal +import stat +import sys +import unittest +import warnings + +from test import support +from test.support import os_helper +from test.support import warnings_helper +from test.support.os_helper import FakePath +from test.support.script_helper import assert_python_ok + +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + +try: + import pwd +except ImportError: + pwd = None + + +@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 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 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())) + + +class ExportsTests(unittest.TestCase): + def test_os_all(self): + self.assertIn('open', os.__all__) + self.assertIn('walk', os.__all__) + + +class PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + self.enterContext(warnings_helper.check_warnings()) + warnings.filterwarnings('ignore', '.* potential security risk .*', + RuntimeWarning) + + def testNoArgFunctions(self): + # test posix functions which take no arguments and have + # no side-effects which we need to cleanup (e.g., fork, wait, abort) + NO_ARG_FUNCTIONS = [ "ctermid", "getcwd", "getcwdb", "uname", + "times", "getloadavg", + "getegid", "geteuid", "getgid", "getgroups", + "getpid", "getpgrp", "getppid", "getuid", "sync", + ] + + for name in NO_ARG_FUNCTIONS: + posix_func = getattr(posix, name, None) + if posix_func is not None: + with self.subTest(name): + posix_func() + self.assertRaises(TypeError, posix_func, 1) + + @unittest.skipUnless(hasattr(posix, 'statvfs'), + 'test needs posix.statvfs()') + def test_statvfs(self): + self.assertTrue(posix.statvfs(os.curdir)) + + @unittest.skipUnless(hasattr(posix, 'fstatvfs'), + 'test needs posix.fstatvfs()') + def test_fstatvfs(self): + fp = open(os_helper.TESTFN) + try: + self.assertTrue(posix.fstatvfs(fp.fileno())) + self.assertTrue(posix.statvfs(fp.fileno())) + finally: + fp.close() + + @unittest.skipUnless(hasattr(posix, 'confstr'), + 'test needs posix.confstr()') + def test_confstr(self): + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.confstr("CS_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.confstr(1.23) + + path = posix.confstr("CS_PATH") + self.assertGreater(len(path), 0) + self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) + + @unittest.skipUnless(hasattr(posix, 'sysconf'), + 'test needs posix.sysconf()') + def test_sysconf(self): + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.sysconf("SC_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.sysconf(1.23) + + arg_max = posix.sysconf("SC_ARG_MAX") + self.assertGreater(arg_max, 0) + self.assertEqual( + posix.sysconf(posix.sysconf_names["SC_ARG_MAX"]), arg_max) + + @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") + def test_mkfifo(self): + if sys.platform == "vxworks": + fifo_path = os.path.join("/fifos/", os_helper.TESTFN) + else: + fifo_path = os_helper.TESTFN + os_helper.unlink(fifo_path) + self.addCleanup(os_helper.unlink, fifo_path) + try: + posix.mkfifo(fifo_path, stat.S_IRUSR | stat.S_IWUSR) + except PermissionError as e: + self.skipTest('posix.mkfifo(): %s' % e) + self.assertTrue(stat.S_ISFIFO(posix.stat(fifo_path).st_mode)) + + @unittest.skipUnless(hasattr(posix, 'mknod') and hasattr(stat, 'S_IFIFO'), + "don't have mknod()/S_IFIFO") + def test_mknod(self): + # Test using mknod() to create a FIFO (the only use specified + # by POSIX). + os_helper.unlink(os_helper.TESTFN) + mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR + try: + posix.mknod(os_helper.TESTFN, mode, 0) + except OSError as e: + # Some old systems don't allow unprivileged users to use + # mknod(), or only support creating device nodes. + self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) + else: + self.assertTrue(stat.S_ISFIFO(posix.stat(os_helper.TESTFN).st_mode)) + + # Keyword arguments are also supported + os_helper.unlink(os_helper.TESTFN) + try: + posix.mknod(path=os_helper.TESTFN, mode=mode, device=0, + dir_fd=None) + except OSError as e: + self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) + + @unittest.skipUnless(hasattr(posix, 'makedev'), 'test needs posix.makedev()') + def test_makedev(self): + st = posix.stat(os_helper.TESTFN) + dev = st.st_dev + self.assertIsInstance(dev, int) + self.assertGreaterEqual(dev, 0) + + major = posix.major(dev) + self.assertIsInstance(major, int) + self.assertGreaterEqual(major, 0) + self.assertEqual(posix.major(dev), major) + self.assertRaises(TypeError, posix.major, float(dev)) + self.assertRaises(TypeError, posix.major) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.major, x) + + minor = posix.minor(dev) + self.assertIsInstance(minor, int) + self.assertGreaterEqual(minor, 0) + self.assertEqual(posix.minor(dev), minor) + self.assertRaises(TypeError, posix.minor, float(dev)) + self.assertRaises(TypeError, posix.minor) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.minor, x) + + self.assertEqual(posix.makedev(major, minor), dev) + self.assertRaises(TypeError, posix.makedev, float(major), minor) + self.assertRaises(TypeError, posix.makedev, major, float(minor)) + self.assertRaises(TypeError, posix.makedev, major) + self.assertRaises(TypeError, posix.makedev) + for x in -2, 2**32, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.makedev, x, minor) + self.assertRaises((ValueError, OverflowError), posix.makedev, major, x) + + # The following tests are needed to test functions accepting or + # returning the special value NODEV (if it is defined). major(), minor() + # and makefile() are the only easily reproducible examples, but that + # behavior is platform specific -- on some platforms their code has + # a special case for NODEV, on others this is just an implementation + # artifact. + if (hasattr(posix, 'NODEV') and + sys.platform.startswith(('linux', 'macos', 'freebsd', 'dragonfly', + 'sunos'))): + NODEV = posix.NODEV + self.assertEqual(posix.major(NODEV), NODEV) + self.assertEqual(posix.minor(NODEV), NODEV) + self.assertEqual(posix.makedev(NODEV, NODEV), NODEV) + + def test_nodev(self): + # NODEV is not a part of Posix, but is defined on many systems. + if (not hasattr(posix, 'NODEV') + and (not sys.platform.startswith(('linux', 'macos', 'freebsd', + 'dragonfly', 'netbsd', 'openbsd', + 'sunos')) + or support.linked_to_musl())): + self.skipTest('not defined on this platform') + self.assertHasAttr(posix, 'NODEV') + + @unittest.skipUnless(hasattr(posix, 'strerror'), + 'test needs posix.strerror()') + def test_strerror(self): + self.assertTrue(posix.strerror(0)) + + @unittest.skipUnless(hasattr(signal, 'SIGCHLD'), 'CLD_XXXX be placed in si_code for a SIGCHLD signal') + @unittest.skipUnless(hasattr(os, 'waitid_result'), "test needs os.waitid_result") + def test_cld_xxxx_constants(self): + os.CLD_EXITED + os.CLD_KILLED + os.CLD_DUMPED + os.CLD_TRAPPED + os.CLD_STOPPED + os.CLD_CONTINUED + + @unittest.skipIf(support.is_wasi, "No dynamic linking on WASI") + @unittest.skipUnless(os.name == 'posix', "POSIX-only test") + def test_rtld_constants(self): + # check presence of major RTLD_* constants + posix.RTLD_LAZY + posix.RTLD_NOW + posix.RTLD_GLOBAL + posix.RTLD_LOCAL + + def test_path_error2(self): + """ + Test functions that call path_error2(), providing two filenames in their exceptions. + """ + for name in ("rename", "replace", "link"): + function = getattr(os, name, None) + if function is None: + continue + + for dst in ("noodly2", os_helper.TESTFN): + try: + function('doesnotexistfilename', dst) + except OSError as e: + self.assertIn("'doesnotexistfilename' -> '{}'".format(dst), str(e)) + break + else: + self.fail("No valid path_error2() test for os." + name) + + def test_path_with_null_character(self): + fn = os_helper.TESTFN + fn_with_NUL = fn + '\0' + self.addCleanup(os_helper.unlink, fn) + os_helper.unlink(fn) + fd = None + try: + with self.assertRaises(ValueError): + fd = os.open(fn_with_NUL, os.O_WRONLY | os.O_CREAT) # raises + finally: + if fd is not None: + os.close(fd) + self.assertFalse(os.path.exists(fn)) + self.assertRaises(ValueError, os.mkdir, fn_with_NUL) + self.assertFalse(os.path.exists(fn)) + open(fn, 'wb').close() + self.assertRaises(ValueError, os.stat, fn_with_NUL) + + def test_path_with_null_byte(self): + fn = os.fsencode(os_helper.TESTFN) + fn_with_NUL = fn + b'\0' + self.addCleanup(os_helper.unlink, fn) + os_helper.unlink(fn) + fd = None + try: + with self.assertRaises(ValueError): + fd = os.open(fn_with_NUL, os.O_WRONLY | os.O_CREAT) # raises + finally: + if fd is not None: + os.close(fd) + self.assertFalse(os.path.exists(fn)) + self.assertRaises(ValueError, os.mkdir, fn_with_NUL) + self.assertFalse(os.path.exists(fn)) + open(fn, 'wb').close() + self.assertRaises(ValueError, os.stat, fn_with_NUL) + + @unittest.skipUnless(hasattr(os, "pidfd_open"), "pidfd_open unavailable") + def test_pidfd_open(self): + with self.assertRaises(OSError) as cm: + os.pidfd_open(-1) + if cm.exception.errno == errno.ENOSYS: + self.skipTest("system does not support pidfd_open") + if isinstance(cm.exception, PermissionError): + self.skipTest(f"pidfd_open syscall blocked: {cm.exception!r}") + self.assertEqual(cm.exception.errno, errno.EINVAL) + os.close(os.pidfd_open(os.getpid(), 0)) + + +class NamespacesTests(unittest.TestCase): + """Tests for os.unshare() and os.setns().""" + + @unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()') + @unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()') + @unittest.skipUnless(os.path.exists('/proc/self/ns/uts'), 'need /proc/self/ns/uts') + @support.requires_linux_version(3, 0, 0) + def test_unshare_setns(self): + code = """if 1: + import errno + import os + import sys + fd = os.open('/proc/self/ns/uts', os.O_RDONLY) + try: + original = os.readlink('/proc/self/ns/uts') + try: + os.unshare(os.CLONE_NEWUTS) + except OSError as e: + if e.errno == errno.ENOSPC: + # skip test if limit is exceeded + sys.exit() + raise + new = os.readlink('/proc/self/ns/uts') + if original == new: + raise Exception('os.unshare failed') + os.setns(fd, os.CLONE_NEWUTS) + restored = os.readlink('/proc/self/ns/uts') + if original != restored: + raise Exception('os.setns failed') + except PermissionError: + # The calling process did not have the required privileges + # for this operation + pass + except OSError as e: + # Skip the test on these errors: + # - ENOSYS: syscall not available + # - EINVAL: kernel was not configured with the CONFIG_UTS_NS option + # - ENOMEM: not enough memory + if e.errno not in (errno.ENOSYS, errno.EINVAL, errno.ENOMEM): + raise + finally: + os.close(fd) + """ + + assert_python_ok("-c", code) + + +def tearDownModule(): + support.reap_children() + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_process.py b/Lib/test/test_os/test_process.py new file mode 100644 index 00000000000000..0fe21f7f04e748 --- /dev/null +++ b/Lib/test/test_os/test_process.py @@ -0,0 +1,1033 @@ +""" +Tests processes: spawn(), exec(), fork(), waitpid(), etc. +""" + +import contextlib +import errno +import os +import platform +import signal +import stat +import subprocess +import sys +import tempfile +import textwrap +import unittest +import uuid +from test import support +from test.support import os_helper +from test.support import warnings_helper +from test.support.os_helper import FakePath +from test.support.script_helper import assert_python_ok +from .utils import requires_sched + +try: + import posix +except ImportError: + import nt as posix +try: + import _testcapi +except ImportError: + _testcapi = None + + +# 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 + +def requires_os_func(name): + return unittest.skipUnless(hasattr(os, name), 'requires os.%s' % name) + + +def tearDownModule(): + support.reap_children() + + +@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') + + +@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 + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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, '') + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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, '', {}) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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, ['']) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @requires_os_func('spawnve') + def test_spawnve_invalid_env(self): + self._test_invalid_env(os.spawnve) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @requires_os_func('spawnvpe') + def test_spawnvpe_invalid_env(self): + self._test_invalid_env(os.spawnvpe) + + +class PosixTester(unittest.TestCase): + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @unittest.skipUnless(getattr(os, 'execve', None) in os.supports_fd, "test needs execve() to support the fd parameter") + @support.requires_fork() + def test_fexecve(self): + fp = os.open(sys.executable, os.O_RDONLY) + try: + pid = os.fork() + if pid == 0: + os.chdir(os.path.split(sys.executable)[0]) + posix.execve(fp, [sys.executable, '-c', 'pass'], os.environ) + else: + support.wait_process(pid, exitcode=0) + finally: + os.close(fp) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()") + @support.requires_fork() + def test_waitid(self): + pid = os.fork() + if pid == 0: + os.chdir(os.path.split(sys.executable)[0]) + posix.execve(sys.executable, [sys.executable, '-c', 'pass'], os.environ) + else: + res = posix.waitid(posix.P_PID, pid, posix.WEXITED) + self.assertEqual(pid, res.si_pid) + + @support.requires_fork() + def test_register_at_fork(self): + with self.assertRaises(TypeError, msg="Positional args not allowed"): + os.register_at_fork(lambda: None) + with self.assertRaises(TypeError, msg="Args must be callable"): + os.register_at_fork(before=2) + with self.assertRaises(TypeError, msg="Args must be callable"): + os.register_at_fork(after_in_child="three") + with self.assertRaises(TypeError, msg="Args must be callable"): + os.register_at_fork(after_in_parent=b"Five") + with self.assertRaises(TypeError, msg="Args must not be None"): + os.register_at_fork(before=None) + with self.assertRaises(TypeError, msg="Args must not be None"): + os.register_at_fork(after_in_child=None) + with self.assertRaises(TypeError, msg="Args must not be None"): + os.register_at_fork(after_in_parent=None) + with self.assertRaises(TypeError, msg="Invalid arg was allowed"): + # Ensure a combination of valid and invalid is an error. + os.register_at_fork(before=None, after_in_parent=lambda: 3) + with self.assertRaises(TypeError, msg="At least one argument is required"): + # when no arg is passed + os.register_at_fork() + with self.assertRaises(TypeError, msg="Invalid arg was allowed"): + # Ensure a combination of valid and invalid is an error. + os.register_at_fork(before=lambda: None, after_in_child='') + # We test actual registrations in their own process so as not to + # pollute this one. There is no way to unregister for cleanup. + code = """if 1: + import os + + r, w = os.pipe() + fin_r, fin_w = os.pipe() + + os.register_at_fork(before=lambda: os.write(w, b'A')) + os.register_at_fork(after_in_parent=lambda: os.write(w, b'C')) + os.register_at_fork(after_in_child=lambda: os.write(w, b'E')) + os.register_at_fork(before=lambda: os.write(w, b'B'), + after_in_parent=lambda: os.write(w, b'D'), + after_in_child=lambda: os.write(w, b'F')) + + pid = os.fork() + if pid == 0: + # At this point, after-forkers have already been executed + os.close(w) + # Wait for parent to tell us to exit + os.read(fin_r, 1) + os._exit(0) + else: + try: + os.close(w) + with open(r, "rb") as f: + data = f.read() + assert len(data) == 6, data + # Check before-fork callbacks + assert data[:2] == b'BA', data + # Check after-fork callbacks + assert sorted(data[2:]) == list(b'CDEF'), data + assert data.index(b'C') < data.index(b'D'), data + assert data.index(b'E') < data.index(b'F'), data + finally: + os.write(fin_w, b'!') + """ + assert_python_ok('-c', code) + + + +class _PosixSpawnMixin: + # Program which does nothing and exits with status 0 (success) + NOOP_PROGRAM = (sys.executable, '-I', '-S', '-c', 'pass') + spawn_func = None + + def python_args(self, *args): + # Disable site module to avoid side effects. For example, + # on Fedora 28, if the HOME environment variable is not set, + # site._getuserbase() calls pwd.getpwuid() which opens + # /var/lib/sss/mc/passwd but then leaves the file open which makes + # test_close_file() to fail. + return (sys.executable, '-I', '-S', *args) + + def test_returns_pid(self): + pidfile = os_helper.TESTFN + self.addCleanup(os_helper.unlink, pidfile) + script = f"""if 1: + import os + with open({pidfile!r}, "w") as pidfile: + pidfile.write(str(os.getpid())) + """ + args = self.python_args('-c', script) + pid = self.spawn_func(args[0], args, os.environ) + support.wait_process(pid, exitcode=0) + with open(pidfile, encoding="utf-8") as f: + self.assertEqual(f.read(), str(pid)) + + def test_no_such_executable(self): + no_such_executable = 'no_such_executable' + try: + pid = self.spawn_func(no_such_executable, + [no_such_executable], + os.environ) + # bpo-35794: PermissionError can be raised if there are + # directories in the $PATH that are not accessible. + except (FileNotFoundError, PermissionError) as exc: + self.assertEqual(exc.filename, no_such_executable) + else: + pid2, status = os.waitpid(pid, 0) + self.assertEqual(pid2, pid) + self.assertNotEqual(status, 0) + + def test_specify_environment(self): + envfile = os_helper.TESTFN + self.addCleanup(os_helper.unlink, envfile) + script = f"""if 1: + import os + with open({envfile!r}, "w", encoding="utf-8") as envfile: + envfile.write(os.environ['foo']) + """ + args = self.python_args('-c', script) + pid = self.spawn_func(args[0], args, + {**os.environ, 'foo': 'bar'}) + support.wait_process(pid, exitcode=0) + with open(envfile, encoding="utf-8") as f: + self.assertEqual(f.read(), 'bar') + + def test_none_file_actions(self): + pid = self.spawn_func( + self.NOOP_PROGRAM[0], + self.NOOP_PROGRAM, + os.environ, + file_actions=None + ) + support.wait_process(pid, exitcode=0) + + def test_empty_file_actions(self): + pid = self.spawn_func( + self.NOOP_PROGRAM[0], + self.NOOP_PROGRAM, + os.environ, + file_actions=[] + ) + support.wait_process(pid, exitcode=0) + + def test_resetids_explicit_default(self): + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', 'pass'], + os.environ, + resetids=False + ) + support.wait_process(pid, exitcode=0) + + def test_resetids(self): + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', 'pass'], + os.environ, + resetids=True + ) + support.wait_process(pid, exitcode=0) + + def test_setpgroup(self): + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', 'pass'], + os.environ, + setpgroup=os.getpgrp() + ) + support.wait_process(pid, exitcode=0) + + def test_setpgroup_wrong_type(self): + with self.assertRaises(TypeError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setpgroup="023") + + @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), + 'need signal.pthread_sigmask()') + def test_setsigmask(self): + code = textwrap.dedent("""\ + import signal + signal.raise_signal(signal.SIGUSR1)""") + + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', code], + os.environ, + setsigmask=[signal.SIGUSR1] + ) + support.wait_process(pid, exitcode=0) + + def test_setsigmask_wrong_type(self): + with self.assertRaises(TypeError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigmask=34) + with self.assertRaises(TypeError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigmask=["j"]) + with self.assertRaises(ValueError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigmask=[signal.NSIG, + signal.NSIG+1]) + + def test_setsid(self): + rfd, wfd = os.pipe() + self.addCleanup(os.close, rfd) + try: + os.set_inheritable(wfd, True) + + code = textwrap.dedent(f""" + import os + fd = {wfd} + sid = os.getsid(0) + os.write(fd, str(sid).encode()) + """) + + try: + pid = self.spawn_func(sys.executable, + [sys.executable, "-c", code], + os.environ, setsid=True) + except NotImplementedError as exc: + self.skipTest(f"setsid is not supported: {exc!r}") + except PermissionError as exc: + self.skipTest(f"setsid failed with: {exc!r}") + finally: + os.close(wfd) + + support.wait_process(pid, exitcode=0) + + output = os.read(rfd, 100) + child_sid = int(output) + parent_sid = os.getsid(os.getpid()) + self.assertNotEqual(parent_sid, child_sid) + + @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), + 'need signal.pthread_sigmask()') + def test_setsigdef(self): + original_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN) + code = textwrap.dedent("""\ + import signal + signal.raise_signal(signal.SIGUSR1)""") + try: + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', code], + os.environ, + setsigdef=[signal.SIGUSR1] + ) + finally: + signal.signal(signal.SIGUSR1, original_handler) + + support.wait_process(pid, exitcode=-signal.SIGUSR1) + + def test_setsigdef_wrong_type(self): + with self.assertRaises(TypeError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigdef=34) + with self.assertRaises(TypeError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigdef=["j"]) + with self.assertRaises(ValueError): + self.spawn_func(sys.executable, + [sys.executable, "-c", "pass"], + os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) + + @requires_sched + @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), + "bpo-34685: test can fail on BSD") + def test_setscheduler_only_param(self): + policy = os.sched_getscheduler(0) + priority = os.sched_get_priority_min(policy) + code = textwrap.dedent(f"""\ + import os, sys + if os.sched_getscheduler(0) != {policy}: + sys.exit(101) + if os.sched_getparam(0).sched_priority != {priority}: + sys.exit(102)""") + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', code], + os.environ, + scheduler=(None, os.sched_param(priority)) + ) + support.wait_process(pid, exitcode=0) + + @requires_sched + @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), + "bpo-34685: test can fail on BSD") + @unittest.skipIf(platform.libc_ver()[0] == 'glibc' and + os.sched_getscheduler(0) in [ + os.SCHED_BATCH, + os.SCHED_IDLE, + os.SCHED_DEADLINE], + "Skip test due to glibc posix_spawn policy") + def test_setscheduler_with_policy(self): + policy = os.sched_getscheduler(0) + priority = os.sched_get_priority_min(policy) + code = textwrap.dedent(f"""\ + import os, sys + if os.sched_getscheduler(0) != {policy}: + sys.exit(101) + if os.sched_getparam(0).sched_priority != {priority}: + sys.exit(102)""") + pid = self.spawn_func( + sys.executable, + [sys.executable, '-c', code], + os.environ, + scheduler=(policy, os.sched_param(priority)) + ) + support.wait_process(pid, exitcode=0) + + def test_multiple_file_actions(self): + file_actions = [ + (os.POSIX_SPAWN_OPEN, 3, os.path.realpath(__file__), os.O_RDONLY, 0), + (os.POSIX_SPAWN_CLOSE, 0), + (os.POSIX_SPAWN_DUP2, 1, 4), + ] + pid = self.spawn_func(self.NOOP_PROGRAM[0], + self.NOOP_PROGRAM, + os.environ, + file_actions=file_actions) + support.wait_process(pid, exitcode=0) + + def test_bad_file_actions(self): + args = self.NOOP_PROGRAM + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[None]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[()]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(None,)]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(12345,)]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(os.POSIX_SPAWN_CLOSE,)]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)]) + with self.assertRaises(TypeError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(os.POSIX_SPAWN_CLOSE, None)]) + with self.assertRaises(ValueError): + self.spawn_func(args[0], args, os.environ, + file_actions=[(os.POSIX_SPAWN_OPEN, + 3, __file__ + '\0', + os.O_RDONLY, 0)]) + + def test_open_file(self): + outfile = os_helper.TESTFN + self.addCleanup(os_helper.unlink, outfile) + script = """if 1: + import sys + sys.stdout.write("hello") + """ + file_actions = [ + (os.POSIX_SPAWN_OPEN, 1, outfile, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + stat.S_IRUSR | stat.S_IWUSR), + ] + args = self.python_args('-c', script) + pid = self.spawn_func(args[0], args, os.environ, + file_actions=file_actions) + + support.wait_process(pid, exitcode=0) + with open(outfile, encoding="utf-8") as f: + self.assertEqual(f.read(), 'hello') + + def test_close_file(self): + closefile = os_helper.TESTFN + self.addCleanup(os_helper.unlink, closefile) + script = f"""if 1: + import os + try: + os.fstat(0) + except OSError as e: + with open({closefile!r}, 'w', encoding='utf-8') as closefile: + closefile.write('is closed %d' % e.errno) + """ + args = self.python_args('-c', script) + pid = self.spawn_func(args[0], args, os.environ, + file_actions=[(os.POSIX_SPAWN_CLOSE, 0)]) + + support.wait_process(pid, exitcode=0) + with open(closefile, encoding="utf-8") as f: + self.assertEqual(f.read(), 'is closed %d' % errno.EBADF) + + def test_dup2(self): + dupfile = os_helper.TESTFN + self.addCleanup(os_helper.unlink, dupfile) + script = """if 1: + import sys + sys.stdout.write("hello") + """ + with open(dupfile, "wb") as childfile: + file_actions = [ + (os.POSIX_SPAWN_DUP2, childfile.fileno(), 1), + ] + args = self.python_args('-c', script) + pid = self.spawn_func(args[0], args, os.environ, + file_actions=file_actions) + support.wait_process(pid, exitcode=0) + with open(dupfile, encoding="utf-8") as f: + self.assertEqual(f.read(), 'hello') + + +@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") +@support.requires_subprocess() +class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(os, 'posix_spawn', None) + + +@unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") +@support.requires_subprocess() +class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): + spawn_func = getattr(os, 'posix_spawnp', None) + + @os_helper.skip_unless_symlink + def test_posix_spawnp(self): + # Use a symlink to create a program in its own temporary directory + temp_dir = tempfile.mkdtemp() + self.addCleanup(os_helper.rmtree, temp_dir) + + program = 'posix_spawnp_test_program.exe' + program_fullpath = os.path.join(temp_dir, program) + os.symlink(sys.executable, program_fullpath) + + try: + path = os.pathsep.join((temp_dir, os.environ['PATH'])) + except KeyError: + path = temp_dir # PATH is not set + + spawn_args = (program, '-I', '-S', '-c', 'pass') + code = textwrap.dedent(""" + import os + from test import support + + args = %a + pid = os.posix_spawnp(args[0], args, os.environ) + + support.wait_process(pid, exitcode=0) + """ % (spawn_args,)) + + # Use a subprocess to test os.posix_spawnp() with a modified PATH + # environment variable: posix_spawnp() uses the current environment + # to locate the program, not its environment argument. + args = ('-c', code) + assert_python_ok(*args, PATH=path) + + +@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.") + @unittest.skipIf(_testcapi is None, "requires _testcapi") + 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_finalization(self): + code = """if 1: + import atexit + import os + + class AtFinalization: + def __del__(self): + print("OK") + pid = os.fork() + if pid != 0: + print("shouldn't be printed") + at_finalization = AtFinalization() + """ + _, out, err = assert_python_ok("-c", code) + self.assertEqual(b"OK\n", out) + self.assertIn(b"can't fork at interpreter shutdown", err) + + +@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()) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + 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_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) + + +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) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_random.py b/Lib/test/test_os/test_random.py new file mode 100644 index 00000000000000..d1075853a485eb --- /dev/null +++ b/Lib/test/test_os/test_random.py @@ -0,0 +1,183 @@ +""" +Test random bytes: getrandom(), urandom(), etc. +""" + +import errno +import os +import sys +import sysconfig +import unittest +from test.support import os_helper +from test.support.script_helper import assert_python_ok +from .utils import create_file + +try: + import resource +except ImportError: + resource = None + + +@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.assertHasAttr(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) + + +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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_scandir.py b/Lib/test/test_os/test_scandir.py new file mode 100644 index 00000000000000..273b652abf66b5 --- /dev/null +++ b/Lib/test/test_os/test_scandir.py @@ -0,0 +1,388 @@ +""" +Test os.scandir() and os.DirEntry. +""" + +import os +import stat +import sys +import unittest +from test import support +from test.support import os_helper +from test.support import warnings_helper +from .utils import create_file + + +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), "") + + 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 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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_scheduler.py b/Lib/test/test_os/test_scheduler.py new file mode 100644 index 00000000000000..7dd2f313f49255 --- /dev/null +++ b/Lib/test/test_os/test_scheduler.py @@ -0,0 +1,227 @@ +""" +Test the scheduler: sched_setaffinity(), sched_yield(), cpu_count(), etc. +""" + +import copy +import errno +import os +import pickle +import sys +import unittest +from test import support +from test.support.script_helper import assert_python_ok +from .utils import requires_sched + +try: + import posix +except ImportError: + import nt as posix + + +class PosixTester(unittest.TestCase): + + requires_sched_h = unittest.skipUnless(hasattr(posix, 'sched_yield'), + "don't have scheduling support") + requires_sched_affinity = unittest.skipUnless(hasattr(posix, 'sched_setaffinity'), + "don't have sched affinity support") + + @requires_sched_h + def test_sched_yield(self): + # This has no error conditions (at least on Linux). + posix.sched_yield() + + @requires_sched_h + @unittest.skipUnless(hasattr(posix, 'sched_get_priority_max'), + "requires sched_get_priority_max()") + def test_sched_priority(self): + # Round-robin usually has interesting priorities. + pol = posix.SCHED_RR + lo = posix.sched_get_priority_min(pol) + hi = posix.sched_get_priority_max(pol) + self.assertIsInstance(lo, int) + self.assertIsInstance(hi, int) + self.assertGreaterEqual(hi, lo) + # Apple platforms return 15 without checking the argument. + if not support.is_apple: + self.assertRaises(OSError, posix.sched_get_priority_min, -23) + self.assertRaises(OSError, posix.sched_get_priority_max, -23) + + @requires_sched + def test_get_and_set_scheduler_and_param(self): + possible_schedulers = [sched for name, sched in posix.__dict__.items() + if name.startswith("SCHED_")] + mine = posix.sched_getscheduler(0) + self.assertIn(mine, possible_schedulers) + try: + parent = posix.sched_getscheduler(os.getppid()) + except PermissionError: + # POSIX specifies EPERM, but Android returns EACCES. Both errno + # values are mapped to PermissionError. + pass + else: + self.assertIn(parent, possible_schedulers) + self.assertRaises(OSError, posix.sched_getscheduler, -1) + self.assertRaises(OSError, posix.sched_getparam, -1) + param = posix.sched_getparam(0) + self.assertIsInstance(param.sched_priority, int) + + # POSIX states that calling sched_setparam() or sched_setscheduler() on + # a process with a scheduling policy other than SCHED_FIFO or SCHED_RR + # is implementation-defined: NetBSD and FreeBSD can return EINVAL. + if not sys.platform.startswith(('freebsd', 'netbsd')): + try: + posix.sched_setscheduler(0, mine, param) + posix.sched_setparam(0, param) + except PermissionError: + pass + self.assertRaises(OSError, posix.sched_setparam, -1, param) + + self.assertRaises(OSError, posix.sched_setscheduler, -1, mine, param) + self.assertRaises(TypeError, posix.sched_setscheduler, 0, mine, None) + self.assertRaises(TypeError, posix.sched_setparam, 0, 43) + param = posix.sched_param(None) + self.assertRaises(TypeError, posix.sched_setparam, 0, param) + large = 214748364700 + param = posix.sched_param(large) + self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + param = posix.sched_param(sched_priority=-large) + self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + + @requires_sched + def test_sched_param(self): + param = posix.sched_param(1) + for proto in range(pickle.HIGHEST_PROTOCOL+1): + newparam = pickle.loads(pickle.dumps(param, proto)) + self.assertEqual(newparam, param) + newparam = copy.copy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.deepcopy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param, sched_priority=0) + self.assertNotEqual(newparam, param) + self.assertEqual(newparam.sched_priority, 0) + + @unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function") + def test_sched_rr_get_interval(self): + try: + interval = posix.sched_rr_get_interval(0) + except OSError as e: + # This likely means that sched_rr_get_interval is only valid for + # processes with the SCHED_RR scheduler in effect. + if e.errno != errno.EINVAL: + raise + self.skipTest("only works on SCHED_RR processes") + self.assertIsInstance(interval, float) + # Reasonable constraints, I think. + self.assertGreaterEqual(interval, 0.) + self.assertLess(interval, 1.) + + @requires_sched_affinity + def test_sched_getaffinity(self): + mask = posix.sched_getaffinity(0) + self.assertIsInstance(mask, set) + self.assertGreaterEqual(len(mask), 1) + if not sys.platform.startswith("freebsd"): + # bpo-47205: does not raise OSError on FreeBSD + self.assertRaises(OSError, posix.sched_getaffinity, -1) + for cpu in mask: + self.assertIsInstance(cpu, int) + self.assertGreaterEqual(cpu, 0) + self.assertLess(cpu, 1 << 32) + + @requires_sched_affinity + def test_sched_setaffinity(self): + mask = posix.sched_getaffinity(0) + self.addCleanup(posix.sched_setaffinity, 0, list(mask)) + + if len(mask) > 1: + # Empty masks are forbidden + mask.pop() + posix.sched_setaffinity(0, mask) + self.assertEqual(posix.sched_getaffinity(0), mask) + + try: + posix.sched_setaffinity(0, []) + # gh-117061: On RHEL9, sched_setaffinity(0, []) does not fail + except OSError: + # sched_setaffinity() manual page documents EINVAL error + # when the mask is empty. + pass + + self.assertRaises(ValueError, posix.sched_setaffinity, 0, [-10]) + self.assertRaises(ValueError, posix.sched_setaffinity, 0, map(int, "0X")) + self.assertRaises(OverflowError, posix.sched_setaffinity, 0, [1<<128]) + if not sys.platform.startswith("freebsd"): + # bpo-47205: does not raise OSError on FreeBSD + self.assertRaises(OSError, posix.sched_setaffinity, -1, mask) + + +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) + + +@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) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_sendfile.py b/Lib/test/test_os/test_sendfile.py new file mode 100644 index 00000000000000..b7dd7e579d4065 --- /dev/null +++ b/Lib/test/test_os/test_sendfile.py @@ -0,0 +1,242 @@ +""" +Test os.sendfile(). +""" + +import asyncio +import errno +import os +import socket +import sys +import unittest +from test.support import os_helper +from test.support import socket_helper +from .utils import create_file + + +def tearDownModule(): + asyncio.events._set_event_loop_policy(None) + + +@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_invalid_count(self): + with self.assertRaises(ValueError, msg="count cannot be negative"): + await self.sendfile_wrapper(self.sockno, self.fileno, offset=0, + count=-1) + + 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 + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_stat.py b/Lib/test/test_os/test_stat.py new file mode 100644 index 00000000000000..d94d145dd6e089 --- /dev/null +++ b/Lib/test/test_os/test_stat.py @@ -0,0 +1,301 @@ +""" +Test os.stat(), os.fstat(), etc. +""" + +import errno +import os +import pickle +import stat +import subprocess +import sys +import unittest +from test.support import os_helper +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + + +# 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.assertHasAttr(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 PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + def test_stat(self): + self.assertTrue(posix.stat(os_helper.TESTFN)) + self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN))) + + self.assertRaisesRegex(TypeError, + 'should be string, bytes, os.PathLike or integer, not', + posix.stat, bytearray(os.fsencode(os_helper.TESTFN))) + self.assertRaisesRegex(TypeError, + 'should be string, bytes, os.PathLike or integer, not', + posix.stat, None) + self.assertRaisesRegex(TypeError, + 'should be string, bytes, os.PathLike or integer, not', + posix.stat, list(os_helper.TESTFN)) + self.assertRaisesRegex(TypeError, + 'should be string, bytes, os.PathLike or integer, not', + posix.stat, list(os.fsencode(os_helper.TESTFN))) + + @unittest.skipUnless(hasattr(posix, 'fstat'), + 'test needs posix.fstat()') + def test_fstat(self): + fp = open(os_helper.TESTFN) + try: + self.assertTrue(posix.fstat(fp.fileno())) + self.assertTrue(posix.stat(fp.fileno())) + + self.assertRaisesRegex(TypeError, + 'should be string, bytes, os.PathLike or integer, not', + posix.stat, float(fp.fileno())) + finally: + fp.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_terminal.py b/Lib/test/test_os/test_terminal.py new file mode 100644 index 00000000000000..f9fab60c1ce582 --- /dev/null +++ b/Lib/test/test_os/test_terminal.py @@ -0,0 +1,187 @@ +""" +Test the terminal: os.get_terminal_size(), os.openpty(), etc. +""" + +import errno +import os +import subprocess +import sys +import textwrap +import unittest +from test import support +from test.support import os_helper +from test.support import warnings_helper + + +@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: + known_errnos = [errno.EINVAL, errno.ENOTTY] + if sys.platform == "android": + # The Android testbed redirects the native stdout to a pipe, + # which returns a different error code. + known_errnos.append(errno.EACCES) + if sys.platform == "win32" or e.errno in known_errnos: + # 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, '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)) + + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + @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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_timerfd.py b/Lib/test/test_os/test_timerfd.py new file mode 100644 index 00000000000000..9ce7b5da1d1c12 --- /dev/null +++ b/Lib/test/test_os/test_timerfd.py @@ -0,0 +1,345 @@ +""" +Test the timerfd API. +""" + +import errno +import os +import select +import selectors +import sys +import time +import unittest +from test import support + + +@unittest.skipUnless(hasattr(os, 'timerfd_create'), 'requires os.timerfd_create') +@unittest.skipIf(sys.platform == "android", "gh-124873: Test is flaky on Android") +@support.requires_linux_version(2, 6, 30) +class TimerfdTests(unittest.TestCase): + # gh-126112: Use 10 ms to tolerate slow buildbots + CLOCK_RES_PLACES = 2 # 10 ms + CLOCK_RES = 10 ** -CLOCK_RES_PLACES + CLOCK_RES_NS = 10 ** (9 - CLOCK_RES_PLACES) + + 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=self.CLOCK_RES_PLACES) + self.assertAlmostEqual(next_expiration, 0.0, places=self.CLOCK_RES_PLACES) + + # 2nd call + next_expiration, interval2 = os.timerfd_settime(fd, initial=initial_expiration, interval=interval) + self.assertAlmostEqual(interval2, interval, places=self.CLOCK_RES_PLACES) + self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) + + # timerfd_gettime + next_expiration, interval2 = os.timerfd_gettime(fd) + self.assertAlmostEqual(interval2, interval, places=self.CLOCK_RES_PLACES) + self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) + + 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) + + @unittest.skipIf(sys.platform.startswith('netbsd'), + "gh-131263: Skip on NetBSD due to system freeze " + "with negative timer values") + 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=self.CLOCK_RES_PLACES) + self.assertAlmostEqual(next_expiration, initial_expiration, places=self.CLOCK_RES_PLACES) + + 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=self.CLOCK_RES_PLACES) + self.assertAlmostEqual(next_expiration, offset, places=self.CLOCK_RES_PLACES) + + 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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_user.py b/Lib/test/test_os/test_user.py new file mode 100644 index 00000000000000..33da8da70ad839 --- /dev/null +++ b/Lib/test/test_os/test_user.py @@ -0,0 +1,260 @@ +""" +Test user functions: getuid(), setgid(), getgroups(), getlogin(), etc. +""" + +import errno +import os +import subprocess +import sys +import unittest +from test import support + +try: + import posix +except ImportError: + import nt as posix + +try: + import pwd +except ImportError: + pwd = None + +# 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) + + +class PosixTester(unittest.TestCase): + @unittest.skipUnless(hasattr(posix, 'getresuid'), + 'test needs posix.getresuid()') + def test_getresuid(self): + user_ids = posix.getresuid() + self.assertEqual(len(user_ids), 3) + for val in user_ids: + self.assertGreaterEqual(val, 0) + + @unittest.skipUnless(hasattr(posix, 'getresgid'), + 'test needs posix.getresgid()') + def test_getresgid(self): + group_ids = posix.getresgid() + self.assertEqual(len(group_ids), 3) + for val in group_ids: + self.assertGreaterEqual(val, 0) + + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid(self): + current_user_ids = posix.getresuid() + self.assertIsNone(posix.setresuid(*current_user_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresuid(-1, -1, -1)) + + @unittest.skipUnless(hasattr(posix, 'setresuid'), + 'test needs posix.setresuid()') + def test_setresuid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_user_ids = posix.getresuid() + if 0 not in current_user_ids: + new_user_ids = (current_user_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresuid, *new_user_ids) + + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid(self): + current_group_ids = posix.getresgid() + self.assertIsNone(posix.setresgid(*current_group_ids)) + # -1 means don't change that value. + self.assertIsNone(posix.setresgid(-1, -1, -1)) + + @unittest.skipUnless(hasattr(posix, 'setresgid'), + 'test needs posix.setresgid()') + def test_setresgid_exception(self): + # Don't do this test if someone is silly enough to run us as root. + current_group_ids = posix.getresgid() + if 0 not in current_group_ids: + new_group_ids = (current_group_ids[0]+1, -1, -1) + self.assertRaises(OSError, posix.setresgid, *new_group_ids) + + @unittest.skipUnless(hasattr(posix, 'initgroups'), + "test needs os.initgroups()") + @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()") + def test_initgroups(self): + # It takes a string and an integer; check that it raises a TypeError + # for other argument lists. + self.assertRaises(TypeError, posix.initgroups) + self.assertRaises(TypeError, posix.initgroups, None) + self.assertRaises(TypeError, posix.initgroups, 3, "foo") + self.assertRaises(TypeError, posix.initgroups, "foo", 3, object()) + + # If a non-privileged user invokes it, it should fail with OSError + # EPERM. + if os.getuid() != 0: + try: + name = pwd.getpwuid(posix.getuid()).pw_name + except KeyError: + # the current UID may not have a pwd entry + raise unittest.SkipTest("need a pwd entry") + try: + posix.initgroups(name, 13) + except OSError as e: + self.assertEqual(e.errno, errno.EPERM) + else: + self.fail("Expected OSError to be raised by initgroups") + + @unittest.skipUnless(hasattr(posix, 'getgrouplist'), "test needs posix.getgrouplist()") + @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()") + @unittest.skipUnless(hasattr(os, 'getuid'), "test needs os.getuid()") + def test_getgrouplist(self): + user = pwd.getpwuid(os.getuid())[0] + group = pwd.getpwuid(os.getuid())[3] + self.assertIn(group, posix.getgrouplist(user, group)) + + @unittest.skipUnless(hasattr(os, 'getegid'), "test needs os.getegid()") + @unittest.skipUnless(hasattr(os, 'popen'), "test needs os.popen()") + @support.requires_subprocess() + def test_getgroups(self): + with os.popen('id -G 2>/dev/null') as idg: + groups = idg.read().strip() + ret = idg.close() + + try: + idg_groups = set(int(g) for g in groups.split()) + except ValueError: + idg_groups = set() + if ret is not None or not idg_groups: + raise unittest.SkipTest("need working 'id -G'") + + # Issues 16698: OS X ABIs prior to 10.6 have limits on getgroups() + if sys.platform == 'darwin': + import sysconfig + dt = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') or '10.3' + if tuple(int(n) for n in dt.split('.')[0:2]) < (10, 6): + raise unittest.SkipTest("getgroups(2) is broken prior to 10.6") + + # 'id -G' and 'os.getgroups()' should return the same + # groups, ignoring order, duplicates, and the effective gid. + # #10822/#26944 - It is implementation defined whether + # posix.getgroups() includes the effective gid. + symdiff = idg_groups.symmetric_difference(posix.getgroups()) + self.assertTrue(not symdiff or symdiff == {posix.getegid()}) + + +class PosixGroupsTester(unittest.TestCase): + + def setUp(self): + if posix.getuid() != 0: + raise unittest.SkipTest("not enough privileges") + if not hasattr(posix, 'getgroups'): + raise unittest.SkipTest("need posix.getgroups") + if sys.platform == 'darwin': + raise unittest.SkipTest("getgroups(2) is broken on OSX") + self.saved_groups = posix.getgroups() + + def tearDown(self): + if hasattr(posix, 'setgroups'): + posix.setgroups(self.saved_groups) + elif hasattr(posix, 'initgroups'): + name = pwd.getpwuid(posix.getuid()).pw_name + posix.initgroups(name, self.saved_groups[0]) + + @unittest.skipUnless(hasattr(posix, 'initgroups'), + "test needs posix.initgroups()") + def test_initgroups(self): + # find missing group + + g = max(self.saved_groups or [0]) + 1 + name = pwd.getpwuid(posix.getuid()).pw_name + posix.initgroups(name, g) + self.assertIn(g, posix.getgroups()) + + @unittest.skipUnless(hasattr(posix, 'setgroups'), + "test needs posix.setgroups()") + def test_setgroups(self): + for groups in [[0], list(range(16))]: + posix.setgroups(groups) + self.assertListEqual(groups, posix.getgroups()) + + +@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)']) + + +# 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) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_os/test_utime.py b/Lib/test/test_os/test_utime.py new file mode 100644 index 00000000000000..d146bc367839b0 --- /dev/null +++ b/Lib/test/test_os/test_utime.py @@ -0,0 +1,357 @@ +""" +Test the os.utime() function. +""" + +import decimal +import fractions +import os +import sys +import time +import unittest +from test import support +from test.support import os_helper +from .utils import create_file + +try: + import posix +except ImportError: + import nt as posix + + +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.is_emscripten: + # Emscripten timestamps are roundtripped through a 53 bit integer of + # nanoseconds. If we want to represent ~50 years which is an 11 + # digits number of seconds: + # 2*log10(60) + log10(24) + log10(365) + log10(60) + log10(50) + # is about 11. Because 53 * log10(2) is about 16, we only have 5 + # digits worth of sub-second precision. + # Some day it would be good to fix this upstream. + delta=1e-5 + self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5) + self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5) + self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5) + self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5) + else: + 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 + + @staticmethod + def ns_to_sec_decimal(ns): + # Convert a number of nanosecond (int) to a number of seconds (Decimal). + # Round towards infinity by adding 0.5 nanosecond to avoid rounding + # issue, os.utime() rounds towards minus infinity. + return decimal.Decimal('1e-9') * ns + decimal.Decimal('0.5e-9') + + @staticmethod + def ns_to_sec_fraction(ns): + # Convert a number of nanosecond (int) to a number of seconds (Fraction). + # Round towards infinity by adding 0.5 nanosecond to avoid rounding + # issue, os.utime() rounds towards minus infinity. + return fractions.Fraction(ns, 10**9) + fractions.Fraction(1, 2*10**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) + + def test_utime_decimal(self): + # pass times as Decimal seconds + def set_time(filename, ns): + atime_ns, mtime_ns = ns + atime = self.ns_to_sec_decimal(atime_ns) + mtime = self.ns_to_sec_decimal(mtime_ns) + os.utime(filename, (atime, mtime)) + self._test_utime(set_time) + + def test_utime_fraction(self): + # pass times as Fraction seconds + def set_time(filename, ns): + atime_ns, mtime_ns = ns + atime = self.ns_to_sec_fraction(atime_ns) + mtime = self.ns_to_sec_fraction(mtime_ns) + os.utime(filename, (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") + + times = ( + 5000000000, # some day in 2128 + # boundaries of the fast path cutoff in posixmodule.c:fill_time + -9223372037, -9223372036, 9223372035, 9223372036, + ) + for large in times: + with self.subTest(large=large): + 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)) + + +class PosixTester(unittest.TestCase): + + def setUp(self): + # create empty file + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + create_file(os_helper.TESTFN, b'') + + @unittest.skipUnless(os.utime in os.supports_fd, "test needs fd support in os.utime") + def test_utime_with_fd(self): + now = time.time() + fd = os.open(os_helper.TESTFN, os.O_RDONLY) + try: + posix.utime(fd) + posix.utime(fd, None) + self.assertRaises(TypeError, posix.utime, fd, (None, None)) + self.assertRaises(TypeError, posix.utime, fd, (now, None)) + self.assertRaises(TypeError, posix.utime, fd, (None, now)) + posix.utime(fd, (int(now), int(now))) + posix.utime(fd, (now, now)) + self.assertRaises(ValueError, posix.utime, fd, (now, now), ns=(now, now)) + self.assertRaises(ValueError, posix.utime, fd, (now, 0), ns=(None, None)) + self.assertRaises(ValueError, posix.utime, fd, (None, None), ns=(now, 0)) + posix.utime(fd, (int(now), int((now - int(now)) * 1e9))) + posix.utime(fd, ns=(int(now), int((now - int(now)) * 1e9))) + + finally: + os.close(fd) + + @unittest.skipUnless(os.utime in os.supports_follow_symlinks, "test needs follow_symlinks support in os.utime") + def test_utime_nofollow_symlinks(self): + now = time.time() + posix.utime(os_helper.TESTFN, None, follow_symlinks=False) + self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, + (None, None), follow_symlinks=False) + self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, + (now, None), follow_symlinks=False) + self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, + (None, now), follow_symlinks=False) + posix.utime(os_helper.TESTFN, (int(now), int(now)), + follow_symlinks=False) + posix.utime(os_helper.TESTFN, (now, now), follow_symlinks=False) + posix.utime(os_helper.TESTFN, follow_symlinks=False) + + @unittest.skipUnless(hasattr(posix, 'utime'), 'test needs posix.utime()') + def test_utime(self): + now = time.time() + posix.utime(os_helper.TESTFN, None) + self.assertRaises(TypeError, posix.utime, + os_helper.TESTFN, (None, None)) + self.assertRaises(TypeError, posix.utime, + os_helper.TESTFN, (now, None)) + self.assertRaises(TypeError, posix.utime, + os_helper.TESTFN, (None, now)) + posix.utime(os_helper.TESTFN, (int(now), int(now))) + posix.utime(os_helper.TESTFN, (now, now)) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/test_windows.py b/Lib/test/test_os/test_windows.py new file mode 100644 index 00000000000000..3af86f085bbb86 --- /dev/null +++ b/Lib/test/test_os/test_windows.py @@ -0,0 +1,690 @@ +""" +Test Windows functions. +""" + +import sys +import unittest + +if sys.platform != "win32": + raise unittest.SkipTest("Win32 specific tests") + +import fnmatch +import mmap +import os +import shutil +import signal +import stat +import subprocess +import textwrap +import time +import uuid +import _winapi +from test import support +from test.support import import_helper +from test.support import os_helper +from test.support import warnings_helper +from .utils import create_file + + +class FileTests(unittest.TestCase): + 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) + + 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' * 100_000)" + self.write_windows_console(sys.executable, "-c", code) + self.write_windows_console(sys.executable, "-u", "-c", code) + + +class PidTests(unittest.TestCase): + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() + 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_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) + + +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) + + +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") + + +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]) + + +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), + ) + + +@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") + + +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)) + + +class Win32NtTests(unittest.TestCase): + def test_getfinalpathname_handles(self): + ctypes = import_helper.import_module('ctypes') + # Ruff false positive -- it thinks we're redefining `ctypes` here + import ctypes.wintypes # noqa: F811 + + 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: + os._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 to the owner (us) + subprocess.check_output([ICACLS, filename, "/grant", "*WD:(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) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_os/utils.py b/Lib/test/test_os/utils.py new file mode 100644 index 00000000000000..0a2f00db936858 --- /dev/null +++ b/Lib/test/test_os/utils.py @@ -0,0 +1,22 @@ +import errno +import os +import unittest + + +def _supports_sched(): + if not hasattr(os, 'sched_getscheduler'): + return False + try: + os.sched_getscheduler(0) + except OSError as e: + if e.errno == errno.ENOSYS: + return False + return True + +requires_sched = unittest.skipUnless(_supports_sched(), 'requires POSIX scheduler API') + + +def create_file(filename, content=b'content'): + with open(filename, "xb", 0) as fp: + if content: + fp.write(content) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py deleted file mode 100644 index ab3d128d08ab47..00000000000000 --- a/Lib/test/test_posix.py +++ /dev/null @@ -1,2531 +0,0 @@ -"Test posix functions" - -from test import support -from test.support import is_apple -from test.support import os_helper -from test.support import warnings_helper -from test.support.script_helper import assert_python_ok - -import copy -import errno -import sys -import signal -import time -import os -import platform -import pickle -import stat -import tempfile -import unittest -import warnings -import textwrap -from contextlib import contextmanager - -try: - import posix -except ImportError: - import nt as posix - -try: - import pwd -except ImportError: - pwd = None - -_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(), - os_helper.TESTFN + '-dummy-symlink') - -requires_32b = unittest.skipUnless( - # Emscripten/WASI have 32 bits pointers, but support 64 bits syscall args. - sys.maxsize < 2**32 and not (support.is_emscripten or support.is_wasi), - 'test is only meaningful on 32-bit builds' -) - -def _supports_sched(): - if not hasattr(posix, 'sched_getscheduler'): - return False - try: - posix.sched_getscheduler(0) - except OSError as e: - if e.errno == errno.ENOSYS: - return False - return True - -requires_sched = unittest.skipUnless(_supports_sched(), 'requires POSIX scheduler API') - - -class PosixTester(unittest.TestCase): - - def setUp(self): - # create empty file - self.addCleanup(os_helper.unlink, os_helper.TESTFN) - with open(os_helper.TESTFN, "wb"): - pass - self.enterContext(warnings_helper.check_warnings()) - warnings.filterwarnings('ignore', '.* potential security risk .*', - RuntimeWarning) - - def testNoArgFunctions(self): - # test posix functions which take no arguments and have - # no side-effects which we need to cleanup (e.g., fork, wait, abort) - NO_ARG_FUNCTIONS = [ "ctermid", "getcwd", "getcwdb", "uname", - "times", "getloadavg", - "getegid", "geteuid", "getgid", "getgroups", - "getpid", "getpgrp", "getppid", "getuid", "sync", - ] - - for name in NO_ARG_FUNCTIONS: - posix_func = getattr(posix, name, None) - if posix_func is not None: - with self.subTest(name): - posix_func() - self.assertRaises(TypeError, posix_func, 1) - - @unittest.skipUnless(hasattr(posix, 'getresuid'), - 'test needs posix.getresuid()') - def test_getresuid(self): - user_ids = posix.getresuid() - self.assertEqual(len(user_ids), 3) - for val in user_ids: - self.assertGreaterEqual(val, 0) - - @unittest.skipUnless(hasattr(posix, 'getresgid'), - 'test needs posix.getresgid()') - def test_getresgid(self): - group_ids = posix.getresgid() - self.assertEqual(len(group_ids), 3) - for val in group_ids: - self.assertGreaterEqual(val, 0) - - @unittest.skipUnless(hasattr(posix, 'setresuid'), - 'test needs posix.setresuid()') - def test_setresuid(self): - current_user_ids = posix.getresuid() - self.assertIsNone(posix.setresuid(*current_user_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresuid(-1, -1, -1)) - - @unittest.skipUnless(hasattr(posix, 'setresuid'), - 'test needs posix.setresuid()') - def test_setresuid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_user_ids = posix.getresuid() - if 0 not in current_user_ids: - new_user_ids = (current_user_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresuid, *new_user_ids) - - @unittest.skipUnless(hasattr(posix, 'setresgid'), - 'test needs posix.setresgid()') - def test_setresgid(self): - current_group_ids = posix.getresgid() - self.assertIsNone(posix.setresgid(*current_group_ids)) - # -1 means don't change that value. - self.assertIsNone(posix.setresgid(-1, -1, -1)) - - @unittest.skipUnless(hasattr(posix, 'setresgid'), - 'test needs posix.setresgid()') - def test_setresgid_exception(self): - # Don't do this test if someone is silly enough to run us as root. - current_group_ids = posix.getresgid() - if 0 not in current_group_ids: - new_group_ids = (current_group_ids[0]+1, -1, -1) - self.assertRaises(OSError, posix.setresgid, *new_group_ids) - - @unittest.skipUnless(hasattr(posix, 'initgroups'), - "test needs os.initgroups()") - @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()") - def test_initgroups(self): - # It takes a string and an integer; check that it raises a TypeError - # for other argument lists. - self.assertRaises(TypeError, posix.initgroups) - self.assertRaises(TypeError, posix.initgroups, None) - self.assertRaises(TypeError, posix.initgroups, 3, "foo") - self.assertRaises(TypeError, posix.initgroups, "foo", 3, object()) - - # If a non-privileged user invokes it, it should fail with OSError - # EPERM. - if os.getuid() != 0: - try: - name = pwd.getpwuid(posix.getuid()).pw_name - except KeyError: - # the current UID may not have a pwd entry - raise unittest.SkipTest("need a pwd entry") - try: - posix.initgroups(name, 13) - except OSError as e: - self.assertEqual(e.errno, errno.EPERM) - else: - self.fail("Expected OSError to be raised by initgroups") - - @unittest.skipUnless(hasattr(posix, 'statvfs'), - 'test needs posix.statvfs()') - def test_statvfs(self): - self.assertTrue(posix.statvfs(os.curdir)) - - @unittest.skipUnless(hasattr(posix, 'fstatvfs'), - 'test needs posix.fstatvfs()') - def test_fstatvfs(self): - fp = open(os_helper.TESTFN) - try: - self.assertTrue(posix.fstatvfs(fp.fileno())) - self.assertTrue(posix.statvfs(fp.fileno())) - finally: - fp.close() - - @unittest.skipUnless(hasattr(posix, 'ftruncate'), - 'test needs posix.ftruncate()') - def test_ftruncate(self): - fp = open(os_helper.TESTFN, 'w+') - try: - # we need to have some data to truncate - fp.write('test') - fp.flush() - posix.ftruncate(fp.fileno(), 0) - finally: - fp.close() - - @unittest.skipUnless(hasattr(posix, 'truncate'), "test needs posix.truncate()") - def test_truncate(self): - with open(os_helper.TESTFN, 'w') as fp: - fp.write('test') - fp.flush() - posix.truncate(os_helper.TESTFN, 0) - - @unittest.skipUnless(getattr(os, 'execve', None) in os.supports_fd, "test needs execve() to support the fd parameter") - @support.requires_fork() - def test_fexecve(self): - fp = os.open(sys.executable, os.O_RDONLY) - try: - pid = os.fork() - if pid == 0: - os.chdir(os.path.split(sys.executable)[0]) - posix.execve(fp, [sys.executable, '-c', 'pass'], os.environ) - else: - support.wait_process(pid, exitcode=0) - finally: - os.close(fp) - - - @unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()") - @support.requires_fork() - def test_waitid(self): - pid = os.fork() - if pid == 0: - os.chdir(os.path.split(sys.executable)[0]) - posix.execve(sys.executable, [sys.executable, '-c', 'pass'], os.environ) - else: - res = posix.waitid(posix.P_PID, pid, posix.WEXITED) - self.assertEqual(pid, res.si_pid) - - @support.requires_fork() - def test_register_at_fork(self): - with self.assertRaises(TypeError, msg="Positional args not allowed"): - os.register_at_fork(lambda: None) - with self.assertRaises(TypeError, msg="Args must be callable"): - os.register_at_fork(before=2) - with self.assertRaises(TypeError, msg="Args must be callable"): - os.register_at_fork(after_in_child="three") - with self.assertRaises(TypeError, msg="Args must be callable"): - os.register_at_fork(after_in_parent=b"Five") - with self.assertRaises(TypeError, msg="Args must not be None"): - os.register_at_fork(before=None) - with self.assertRaises(TypeError, msg="Args must not be None"): - os.register_at_fork(after_in_child=None) - with self.assertRaises(TypeError, msg="Args must not be None"): - os.register_at_fork(after_in_parent=None) - with self.assertRaises(TypeError, msg="Invalid arg was allowed"): - # Ensure a combination of valid and invalid is an error. - os.register_at_fork(before=None, after_in_parent=lambda: 3) - with self.assertRaises(TypeError, msg="At least one argument is required"): - # when no arg is passed - os.register_at_fork() - with self.assertRaises(TypeError, msg="Invalid arg was allowed"): - # Ensure a combination of valid and invalid is an error. - os.register_at_fork(before=lambda: None, after_in_child='') - # We test actual registrations in their own process so as not to - # pollute this one. There is no way to unregister for cleanup. - code = """if 1: - import os - - r, w = os.pipe() - fin_r, fin_w = os.pipe() - - os.register_at_fork(before=lambda: os.write(w, b'A')) - os.register_at_fork(after_in_parent=lambda: os.write(w, b'C')) - os.register_at_fork(after_in_child=lambda: os.write(w, b'E')) - os.register_at_fork(before=lambda: os.write(w, b'B'), - after_in_parent=lambda: os.write(w, b'D'), - after_in_child=lambda: os.write(w, b'F')) - - pid = os.fork() - if pid == 0: - # At this point, after-forkers have already been executed - os.close(w) - # Wait for parent to tell us to exit - os.read(fin_r, 1) - os._exit(0) - else: - try: - os.close(w) - with open(r, "rb") as f: - data = f.read() - assert len(data) == 6, data - # Check before-fork callbacks - assert data[:2] == b'BA', data - # Check after-fork callbacks - assert sorted(data[2:]) == list(b'CDEF'), data - assert data.index(b'C') < data.index(b'D'), data - assert data.index(b'E') < data.index(b'F'), data - finally: - os.write(fin_w, b'!') - """ - assert_python_ok('-c', code) - - @unittest.skipUnless(hasattr(posix, 'lockf'), "test needs posix.lockf()") - def test_lockf(self): - fd = os.open(os_helper.TESTFN, os.O_WRONLY | os.O_CREAT) - try: - os.write(fd, b'test') - os.lseek(fd, 0, os.SEEK_SET) - posix.lockf(fd, posix.F_LOCK, 4) - # section is locked - posix.lockf(fd, posix.F_ULOCK, 4) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'pread'), "test needs posix.pread()") - def test_pread(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b'test') - os.lseek(fd, 0, os.SEEK_SET) - self.assertEqual(b'es', posix.pread(fd, 2, 1)) - # the first pread() shouldn't disturb the file offset - self.assertEqual(b'te', posix.read(fd, 2)) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") - def test_preadv(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b'test1tt2t3t5t6t6t8') - buf = [bytearray(i) for i in [5, 3, 2]] - self.assertEqual(posix.preadv(fd, buf, 3), 10) - self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf)) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") - @unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI") - def test_preadv_flags(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b'test1tt2t3t5t6t6t8') - buf = [bytearray(i) for i in [5, 3, 2]] - self.assertEqual(posix.preadv(fd, buf, 3, os.RWF_HIPRI), 10) - self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf)) - except NotImplementedError: - self.skipTest("preadv2 not available") - except OSError as inst: - # Is possible that the macro RWF_HIPRI was defined at compilation time - # but the option is not supported by the kernel or the runtime libc shared - # library. - if inst.errno in {errno.EINVAL, errno.ENOTSUP}: - raise unittest.SkipTest("RWF_HIPRI is not supported by the current system") - else: - raise - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()") - @requires_32b - def test_preadv_overflow_32bits(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - buf = [bytearray(2**16)] * 2**15 - with self.assertRaises(OSError) as cm: - os.preadv(fd, buf, 0) - self.assertEqual(cm.exception.errno, errno.EINVAL) - self.assertEqual(bytes(buf[0]), b'\0'* 2**16) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()") - def test_pwrite(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b'test') - os.lseek(fd, 0, os.SEEK_SET) - posix.pwrite(fd, b'xx', 1) - self.assertEqual(b'txxt', posix.read(fd, 4)) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") - def test_pwritev(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b"xx") - os.lseek(fd, 0, os.SEEK_SET) - n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2) - self.assertEqual(n, 10) - - os.lseek(fd, 0, os.SEEK_SET) - self.assertEqual(b'xxtest1tt2t3', posix.read(fd, 100)) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") - @unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC") - def test_pwritev_flags(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd,b"xx") - os.lseek(fd, 0, os.SEEK_SET) - n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2, os.RWF_SYNC) - self.assertEqual(n, 10) - - os.lseek(fd, 0, os.SEEK_SET) - self.assertEqual(b'xxtest1tt2', posix.read(fd, 100)) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()") - @requires_32b - def test_pwritev_overflow_32bits(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - with self.assertRaises(OSError) as cm: - os.pwritev(fd, [b"x" * 2**16] * 2**15, 0) - self.assertEqual(cm.exception.errno, errno.EINVAL) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'posix_fallocate'), - "test needs posix.posix_fallocate()") - def test_posix_fallocate(self): - fd = os.open(os_helper.TESTFN, os.O_WRONLY | os.O_CREAT) - try: - posix.posix_fallocate(fd, 0, 10) - except OSError as inst: - # issue10812, ZFS doesn't appear to support posix_fallocate, - # so skip Solaris-based since they are likely to have ZFS. - # issue33655: Also ignore EINVAL on *BSD since ZFS is also - # often used there. - if inst.errno == errno.EINVAL and sys.platform.startswith( - ('sunos', 'freebsd', 'openbsd', 'gnukfreebsd')): - raise unittest.SkipTest("test may fail on ZFS filesystems") - elif inst.errno == errno.EOPNOTSUPP and sys.platform.startswith("netbsd"): - raise unittest.SkipTest("test may fail on FFS filesystems") - else: - raise - finally: - os.close(fd) - - # issue31106 - posix_fallocate() does not set error in errno. - @unittest.skipUnless(hasattr(posix, 'posix_fallocate'), - "test needs posix.posix_fallocate()") - def test_posix_fallocate_errno(self): - try: - posix.posix_fallocate(-42, 0, 10) - except OSError as inst: - if inst.errno != errno.EBADF: - raise - - @unittest.skipUnless(hasattr(posix, 'posix_fadvise'), - "test needs posix.posix_fadvise()") - def test_posix_fadvise(self): - fd = os.open(os_helper.TESTFN, os.O_RDONLY) - try: - posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_WILLNEED) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'posix_fadvise'), - "test needs posix.posix_fadvise()") - def test_posix_fadvise_errno(self): - try: - posix.posix_fadvise(-42, 0, 0, posix.POSIX_FADV_WILLNEED) - except OSError as inst: - if inst.errno != errno.EBADF: - raise - - @unittest.skipUnless(os.utime in os.supports_fd, "test needs fd support in os.utime") - def test_utime_with_fd(self): - now = time.time() - fd = os.open(os_helper.TESTFN, os.O_RDONLY) - try: - posix.utime(fd) - posix.utime(fd, None) - self.assertRaises(TypeError, posix.utime, fd, (None, None)) - self.assertRaises(TypeError, posix.utime, fd, (now, None)) - self.assertRaises(TypeError, posix.utime, fd, (None, now)) - posix.utime(fd, (int(now), int(now))) - posix.utime(fd, (now, now)) - self.assertRaises(ValueError, posix.utime, fd, (now, now), ns=(now, now)) - self.assertRaises(ValueError, posix.utime, fd, (now, 0), ns=(None, None)) - self.assertRaises(ValueError, posix.utime, fd, (None, None), ns=(now, 0)) - posix.utime(fd, (int(now), int((now - int(now)) * 1e9))) - posix.utime(fd, ns=(int(now), int((now - int(now)) * 1e9))) - - finally: - os.close(fd) - - @unittest.skipUnless(os.utime in os.supports_follow_symlinks, "test needs follow_symlinks support in os.utime") - def test_utime_nofollow_symlinks(self): - now = time.time() - posix.utime(os_helper.TESTFN, None, follow_symlinks=False) - self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, - (None, None), follow_symlinks=False) - self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, - (now, None), follow_symlinks=False) - self.assertRaises(TypeError, posix.utime, os_helper.TESTFN, - (None, now), follow_symlinks=False) - posix.utime(os_helper.TESTFN, (int(now), int(now)), - follow_symlinks=False) - posix.utime(os_helper.TESTFN, (now, now), follow_symlinks=False) - posix.utime(os_helper.TESTFN, follow_symlinks=False) - - @unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()") - def test_writev(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - n = os.writev(fd, (b'test1', b'tt2', b't3')) - self.assertEqual(n, 10) - - os.lseek(fd, 0, os.SEEK_SET) - self.assertEqual(b'test1tt2t3', posix.read(fd, 10)) - - # Issue #20113: empty list of buffers should not crash - try: - size = posix.writev(fd, []) - except OSError: - # writev(fd, []) raises OSError(22, "Invalid argument") - # on OpenIndiana - pass - else: - self.assertEqual(size, 0) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()") - @requires_32b - def test_writev_overflow_32bits(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - with self.assertRaises(OSError) as cm: - os.writev(fd, [b"x" * 2**16] * 2**15) - self.assertEqual(cm.exception.errno, errno.EINVAL) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()") - def test_readv(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - os.write(fd, b'test1tt2t3') - os.lseek(fd, 0, os.SEEK_SET) - buf = [bytearray(i) for i in [5, 3, 2]] - self.assertEqual(posix.readv(fd, buf), 10) - self.assertEqual([b'test1', b'tt2', b't3'], [bytes(i) for i in buf]) - - # Issue #20113: empty list of buffers should not crash - try: - size = posix.readv(fd, []) - except OSError: - # readv(fd, []) raises OSError(22, "Invalid argument") - # on OpenIndiana - pass - else: - self.assertEqual(size, 0) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()") - @requires_32b - def test_readv_overflow_32bits(self): - fd = os.open(os_helper.TESTFN, os.O_RDWR | os.O_CREAT) - try: - buf = [bytearray(2**16)] * 2**15 - with self.assertRaises(OSError) as cm: - os.readv(fd, buf) - self.assertEqual(cm.exception.errno, errno.EINVAL) - self.assertEqual(bytes(buf[0]), b'\0'* 2**16) - finally: - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'dup'), - 'test needs posix.dup()') - @unittest.skipIf(support.is_wasi, "WASI does not have dup()") - def test_dup(self): - fp = open(os_helper.TESTFN) - try: - fd = posix.dup(fp.fileno()) - self.assertIsInstance(fd, int) - os.close(fd) - finally: - fp.close() - - @unittest.skipUnless(hasattr(posix, 'confstr'), - 'test needs posix.confstr()') - def test_confstr(self): - with self.assertRaisesRegex( - ValueError, "unrecognized configuration name" - ): - posix.confstr("CS_garbage") - - with self.assertRaisesRegex( - TypeError, "configuration names must be strings or integers" - ): - posix.confstr(1.23) - - path = posix.confstr("CS_PATH") - self.assertGreater(len(path), 0) - self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) - - @unittest.skipUnless(hasattr(posix, 'sysconf'), - 'test needs posix.sysconf()') - def test_sysconf(self): - with self.assertRaisesRegex( - ValueError, "unrecognized configuration name" - ): - posix.sysconf("SC_garbage") - - with self.assertRaisesRegex( - TypeError, "configuration names must be strings or integers" - ): - posix.sysconf(1.23) - - arg_max = posix.sysconf("SC_ARG_MAX") - self.assertGreater(arg_max, 0) - self.assertEqual( - posix.sysconf(posix.sysconf_names["SC_ARG_MAX"]), arg_max) - - @unittest.skipUnless(hasattr(posix, 'dup2'), - 'test needs posix.dup2()') - @unittest.skipIf(support.is_wasi, "WASI does not have dup2()") - def test_dup2(self): - fp1 = open(os_helper.TESTFN) - fp2 = open(os_helper.TESTFN) - try: - posix.dup2(fp1.fileno(), fp2.fileno()) - finally: - fp1.close() - fp2.close() - - @unittest.skipUnless(hasattr(os, 'O_CLOEXEC'), "needs os.O_CLOEXEC") - @support.requires_linux_version(2, 6, 23) - @support.requires_subprocess() - def test_oscloexec(self): - fd = os.open(os_helper.TESTFN, os.O_RDONLY|os.O_CLOEXEC) - self.addCleanup(os.close, fd) - self.assertFalse(os.get_inheritable(fd)) - - @unittest.skipUnless(hasattr(posix, 'O_EXLOCK'), - 'test needs posix.O_EXLOCK') - def test_osexlock(self): - fd = os.open(os_helper.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, os_helper.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) - - if hasattr(posix, "O_SHLOCK"): - fd = os.open(os_helper.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, os_helper.TESTFN, - os.O_WRONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'O_SHLOCK'), - 'test needs posix.O_SHLOCK') - def test_osshlock(self): - fd1 = os.open(os_helper.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - fd2 = os.open(os_helper.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - os.close(fd2) - os.close(fd1) - - if hasattr(posix, "O_EXLOCK"): - fd = os.open(os_helper.TESTFN, - os.O_WRONLY|os.O_SHLOCK|os.O_CREAT) - self.assertRaises(OSError, os.open, os_helper.TESTFN, - os.O_RDONLY|os.O_EXLOCK|os.O_NONBLOCK) - os.close(fd) - - @unittest.skipUnless(hasattr(posix, 'fstat'), - 'test needs posix.fstat()') - def test_fstat(self): - fp = open(os_helper.TESTFN) - try: - self.assertTrue(posix.fstat(fp.fileno())) - self.assertTrue(posix.stat(fp.fileno())) - - self.assertRaisesRegex(TypeError, - 'should be string, bytes, os.PathLike or integer, not', - posix.stat, float(fp.fileno())) - finally: - fp.close() - - def test_stat(self): - self.assertTrue(posix.stat(os_helper.TESTFN)) - self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN))) - - self.assertRaisesRegex(TypeError, - 'should be string, bytes, os.PathLike or integer, not', - posix.stat, bytearray(os.fsencode(os_helper.TESTFN))) - self.assertRaisesRegex(TypeError, - 'should be string, bytes, os.PathLike or integer, not', - posix.stat, None) - self.assertRaisesRegex(TypeError, - 'should be string, bytes, os.PathLike or integer, not', - posix.stat, list(os_helper.TESTFN)) - self.assertRaisesRegex(TypeError, - 'should be string, bytes, os.PathLike or integer, not', - posix.stat, list(os.fsencode(os_helper.TESTFN))) - - @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") - def test_mkfifo(self): - if sys.platform == "vxworks": - fifo_path = os.path.join("/fifos/", os_helper.TESTFN) - else: - fifo_path = os_helper.TESTFN - os_helper.unlink(fifo_path) - self.addCleanup(os_helper.unlink, fifo_path) - try: - posix.mkfifo(fifo_path, stat.S_IRUSR | stat.S_IWUSR) - except PermissionError as e: - self.skipTest('posix.mkfifo(): %s' % e) - self.assertTrue(stat.S_ISFIFO(posix.stat(fifo_path).st_mode)) - - @unittest.skipUnless(hasattr(posix, 'mknod') and hasattr(stat, 'S_IFIFO'), - "don't have mknod()/S_IFIFO") - def test_mknod(self): - # Test using mknod() to create a FIFO (the only use specified - # by POSIX). - os_helper.unlink(os_helper.TESTFN) - mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR - try: - posix.mknod(os_helper.TESTFN, mode, 0) - except OSError as e: - # Some old systems don't allow unprivileged users to use - # mknod(), or only support creating device nodes. - self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) - else: - self.assertTrue(stat.S_ISFIFO(posix.stat(os_helper.TESTFN).st_mode)) - - # Keyword arguments are also supported - os_helper.unlink(os_helper.TESTFN) - try: - posix.mknod(path=os_helper.TESTFN, mode=mode, device=0, - dir_fd=None) - except OSError as e: - self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) - - @unittest.skipUnless(hasattr(posix, 'makedev'), 'test needs posix.makedev()') - def test_makedev(self): - st = posix.stat(os_helper.TESTFN) - dev = st.st_dev - self.assertIsInstance(dev, int) - self.assertGreaterEqual(dev, 0) - - major = posix.major(dev) - self.assertIsInstance(major, int) - self.assertGreaterEqual(major, 0) - self.assertEqual(posix.major(dev), major) - self.assertRaises(TypeError, posix.major, float(dev)) - self.assertRaises(TypeError, posix.major) - for x in -2, 2**64, -2**63-1: - self.assertRaises((ValueError, OverflowError), posix.major, x) - - minor = posix.minor(dev) - self.assertIsInstance(minor, int) - self.assertGreaterEqual(minor, 0) - self.assertEqual(posix.minor(dev), minor) - self.assertRaises(TypeError, posix.minor, float(dev)) - self.assertRaises(TypeError, posix.minor) - for x in -2, 2**64, -2**63-1: - self.assertRaises((ValueError, OverflowError), posix.minor, x) - - self.assertEqual(posix.makedev(major, minor), dev) - self.assertRaises(TypeError, posix.makedev, float(major), minor) - self.assertRaises(TypeError, posix.makedev, major, float(minor)) - self.assertRaises(TypeError, posix.makedev, major) - self.assertRaises(TypeError, posix.makedev) - for x in -2, 2**32, 2**64, -2**63-1: - self.assertRaises((ValueError, OverflowError), posix.makedev, x, minor) - self.assertRaises((ValueError, OverflowError), posix.makedev, major, x) - - # The following tests are needed to test functions accepting or - # returning the special value NODEV (if it is defined). major(), minor() - # and makefile() are the only easily reproducible examples, but that - # behavior is platform specific -- on some platforms their code has - # a special case for NODEV, on others this is just an implementation - # artifact. - if (hasattr(posix, 'NODEV') and - sys.platform.startswith(('linux', 'macos', 'freebsd', 'dragonfly', - 'sunos'))): - NODEV = posix.NODEV - self.assertEqual(posix.major(NODEV), NODEV) - self.assertEqual(posix.minor(NODEV), NODEV) - self.assertEqual(posix.makedev(NODEV, NODEV), NODEV) - - def test_nodev(self): - # NODEV is not a part of Posix, but is defined on many systems. - if (not hasattr(posix, 'NODEV') - and (not sys.platform.startswith(('linux', 'macos', 'freebsd', - 'dragonfly', 'netbsd', 'openbsd', - 'sunos')) - or support.linked_to_musl())): - self.skipTest('not defined on this platform') - self.assertHasAttr(posix, 'NODEV') - - def _test_all_chown_common(self, chown_func, first_param, stat_func): - """Common code for chown, fchown and lchown tests.""" - def check_stat(uid, gid): - if stat_func is not None: - stat = stat_func(first_param) - self.assertEqual(stat.st_uid, uid) - self.assertEqual(stat.st_gid, gid) - uid = os.getuid() - gid = os.getgid() - # test a successful chown call - chown_func(first_param, uid, gid) - check_stat(uid, gid) - chown_func(first_param, -1, gid) - check_stat(uid, gid) - chown_func(first_param, uid, -1) - check_stat(uid, gid) - - if sys.platform == "vxworks": - # On VxWorks, root user id is 1 and 0 means no login user: - # both are super users. - is_root = (uid in (0, 1)) - else: - is_root = (uid == 0) - if support.is_emscripten: - # Emscripten getuid() / geteuid() always return 0 (root), but - # cannot chown uid/gid to random value. - pass - elif is_root: - # Try an amusingly large uid/gid to make sure we handle - # large unsigned values. (chown lets you use any - # uid/gid you like, even if they aren't defined.) - # - # On VxWorks uid_t is defined as unsigned short. A big - # value greater than 65535 will result in underflow error. - # - # This problem keeps coming up: - # http://bugs.python.org/issue1747858 - # http://bugs.python.org/issue4591 - # http://bugs.python.org/issue15301 - # Hopefully the fix in 4591 fixes it for good! - # - # This part of the test only runs when run as root. - # Only scary people run their tests as root. - - big_value = (2**31 if sys.platform != "vxworks" else 2**15) - chown_func(first_param, big_value, big_value) - check_stat(big_value, big_value) - chown_func(first_param, -1, -1) - check_stat(big_value, big_value) - chown_func(first_param, uid, gid) - check_stat(uid, gid) - elif platform.system() in ('HP-UX', 'SunOS'): - # HP-UX and Solaris can allow a non-root user to chown() to root - # (issue #5113) - raise unittest.SkipTest("Skipping because of non-standard chown() " - "behavior") - else: - # non-root cannot chown to root, raises OSError - self.assertRaises(OSError, chown_func, first_param, 0, 0) - check_stat(uid, gid) - self.assertRaises(OSError, chown_func, first_param, 0, -1) - check_stat(uid, gid) - if hasattr(os, 'getgroups'): - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) - # test illegal types - for t in str, float: - self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) - check_stat(uid, gid) - self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) - check_stat(uid, gid) - - @unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") - @unittest.skipIf(support.is_emscripten, "getgid() is a stub") - def test_chown(self): - # raise an OSError if the file does not exist - os.unlink(os_helper.TESTFN) - self.assertRaises(OSError, posix.chown, os_helper.TESTFN, -1, -1) - - # re-create the file - os_helper.create_empty_file(os_helper.TESTFN) - self._test_all_chown_common(posix.chown, os_helper.TESTFN, posix.stat) - - @os_helper.skip_unless_working_chmod - @unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()") - @unittest.skipIf(support.is_emscripten, "getgid() is a stub") - def test_fchown(self): - os.unlink(os_helper.TESTFN) - - # re-create the file - test_file = open(os_helper.TESTFN, 'w') - try: - fd = test_file.fileno() - self._test_all_chown_common(posix.fchown, fd, - getattr(posix, 'fstat', None)) - finally: - test_file.close() - - @os_helper.skip_unless_working_chmod - @unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()") - def test_lchown(self): - os.unlink(os_helper.TESTFN) - # create a symlink - os.symlink(_DUMMY_SYMLINK, os_helper.TESTFN) - self._test_all_chown_common(posix.lchown, os_helper.TESTFN, - getattr(posix, 'lstat', None)) - - @unittest.skipUnless(hasattr(posix, 'chdir'), 'test needs posix.chdir()') - def test_chdir(self): - posix.chdir(os.curdir) - self.assertRaises(OSError, posix.chdir, os_helper.TESTFN) - - def test_listdir(self): - self.assertIn(os_helper.TESTFN, posix.listdir(os.curdir)) - - def test_listdir_default(self): - # When listdir is called without argument, - # it's the same as listdir(os.curdir). - self.assertIn(os_helper.TESTFN, posix.listdir()) - - def test_listdir_bytes(self): - # When listdir is called with a bytes object, - # the returned strings are of type bytes. - self.assertIn(os.fsencode(os_helper.TESTFN), posix.listdir(b'.')) - - def test_listdir_bytes_like(self): - for cls in bytearray, memoryview: - with self.assertRaises(TypeError): - posix.listdir(cls(b'.')) - - @unittest.skipUnless(posix.listdir in os.supports_fd, - "test needs fd support for posix.listdir()") - def test_listdir_fd(self): - f = posix.open(posix.getcwd(), posix.O_RDONLY) - self.addCleanup(posix.close, f) - self.assertEqual( - sorted(posix.listdir('.')), - sorted(posix.listdir(f)) - ) - # Check that the fd offset was reset (issue #13739) - self.assertEqual( - sorted(posix.listdir('.')), - sorted(posix.listdir(f)) - ) - - @unittest.skipUnless(hasattr(posix, 'access'), 'test needs posix.access()') - def test_access(self): - self.assertTrue(posix.access(os_helper.TESTFN, os.R_OK)) - - @unittest.skipUnless(hasattr(posix, 'umask'), 'test needs posix.umask()') - def test_umask(self): - old_mask = posix.umask(0) - self.assertIsInstance(old_mask, int) - posix.umask(old_mask) - - @unittest.skipUnless(hasattr(posix, 'strerror'), - 'test needs posix.strerror()') - def test_strerror(self): - self.assertTrue(posix.strerror(0)) - - @unittest.skipUnless(hasattr(posix, 'pipe'), 'test needs posix.pipe()') - def test_pipe(self): - reader, writer = posix.pipe() - os.close(reader) - os.close(writer) - - @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") - @support.requires_linux_version(2, 6, 27) - def test_pipe2(self): - self.assertRaises(TypeError, os.pipe2, 'DEADBEEF') - self.assertRaises(TypeError, os.pipe2, 0, 0) - - # try calling with flags = 0, like os.pipe() - r, w = os.pipe2(0) - os.close(r) - os.close(w) - - # test flags - r, w = os.pipe2(os.O_CLOEXEC|os.O_NONBLOCK) - self.addCleanup(os.close, r) - self.addCleanup(os.close, w) - self.assertFalse(os.get_inheritable(r)) - self.assertFalse(os.get_inheritable(w)) - self.assertFalse(os.get_blocking(r)) - self.assertFalse(os.get_blocking(w)) - # try reading from an empty pipe: this should fail, not block - self.assertRaises(OSError, os.read, r, 1) - # try a write big enough to fill-up the pipe: this should either - # fail or perform a partial write, not block - try: - os.write(w, b'x' * support.PIPE_MAX_SIZE) - except OSError: - pass - - @support.cpython_only - @unittest.skipUnless(hasattr(os, 'pipe2'), "test needs os.pipe2()") - @support.requires_linux_version(2, 6, 27) - def test_pipe2_c_limits(self): - # Issue 15989 - import _testcapi - self.assertRaises(OverflowError, os.pipe2, _testcapi.INT_MAX + 1) - self.assertRaises(OverflowError, os.pipe2, _testcapi.UINT_MAX + 1) - - @unittest.skipUnless(hasattr(posix, 'utime'), 'test needs posix.utime()') - def test_utime(self): - now = time.time() - posix.utime(os_helper.TESTFN, None) - self.assertRaises(TypeError, posix.utime, - os_helper.TESTFN, (None, None)) - self.assertRaises(TypeError, posix.utime, - os_helper.TESTFN, (now, None)) - self.assertRaises(TypeError, posix.utime, - os_helper.TESTFN, (None, now)) - posix.utime(os_helper.TESTFN, (int(now), int(now))) - posix.utime(os_helper.TESTFN, (now, now)) - - def check_chmod(self, chmod_func, target, **kwargs): - closefd = not isinstance(target, int) - mode = os.stat(target).st_mode - try: - new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(target, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, new_mode) - if stat.S_ISREG(mode): - try: - with open(target, 'wb+', closefd=closefd): - pass - except PermissionError: - pass - new_mode = mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(target, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, new_mode) - if stat.S_ISREG(mode): - with open(target, 'wb+', closefd=closefd): - pass - finally: - chmod_func(target, mode) - - @os_helper.skip_unless_working_chmod - def test_chmod_file(self): - self.check_chmod(posix.chmod, os_helper.TESTFN) - - def tempdir(self): - target = os_helper.TESTFN + 'd' - posix.mkdir(target) - self.addCleanup(posix.rmdir, target) - return target - - @os_helper.skip_unless_working_chmod - def test_chmod_dir(self): - target = self.tempdir() - self.check_chmod(posix.chmod, target) - - @os_helper.skip_unless_working_chmod - def test_fchmod_file(self): - with open(os_helper.TESTFN, 'wb+') as f: - self.check_chmod(posix.fchmod, f.fileno()) - self.check_chmod(posix.chmod, f.fileno()) - - @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') - def test_lchmod_file(self): - self.check_chmod(posix.lchmod, os_helper.TESTFN) - self.check_chmod(posix.chmod, os_helper.TESTFN, follow_symlinks=False) - - @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') - def test_lchmod_dir(self): - target = self.tempdir() - self.check_chmod(posix.lchmod, target) - self.check_chmod(posix.chmod, target, follow_symlinks=False) - - def check_chmod_link(self, chmod_func, target, link, **kwargs): - target_mode = os.stat(target).st_mode - link_mode = os.lstat(link).st_mode - try: - new_mode = target_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(link, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, new_mode) - self.assertEqual(os.lstat(link).st_mode, link_mode) - new_mode = target_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(link, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, new_mode) - self.assertEqual(os.lstat(link).st_mode, link_mode) - finally: - posix.chmod(target, target_mode) - - def check_lchmod_link(self, chmod_func, target, link, **kwargs): - target_mode = os.stat(target).st_mode - link_mode = os.lstat(link).st_mode - new_mode = link_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(link, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, target_mode) - self.assertEqual(os.lstat(link).st_mode, new_mode) - new_mode = link_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) - chmod_func(link, new_mode, **kwargs) - self.assertEqual(os.stat(target).st_mode, target_mode) - self.assertEqual(os.lstat(link).st_mode, new_mode) - - @os_helper.skip_unless_symlink - def test_chmod_file_symlink(self): - target = os_helper.TESTFN - link = os_helper.TESTFN + '-link' - os.symlink(target, link) - self.addCleanup(posix.unlink, link) - if os.name == 'nt': - self.check_lchmod_link(posix.chmod, target, link) - else: - self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) - - @os_helper.skip_unless_symlink - def test_chmod_dir_symlink(self): - target = self.tempdir() - link = os_helper.TESTFN + '-link' - os.symlink(target, link, target_is_directory=True) - self.addCleanup(posix.unlink, link) - if os.name == 'nt': - self.check_lchmod_link(posix.chmod, target, link) - else: - self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) - - @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') - @os_helper.skip_unless_symlink - def test_lchmod_file_symlink(self): - target = os_helper.TESTFN - link = os_helper.TESTFN + '-link' - os.symlink(target, link) - self.addCleanup(posix.unlink, link) - self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) - self.check_lchmod_link(posix.lchmod, target, link) - - @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') - @os_helper.skip_unless_symlink - def test_lchmod_dir_symlink(self): - target = self.tempdir() - link = os_helper.TESTFN + '-link' - os.symlink(target, link) - self.addCleanup(posix.unlink, link) - self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) - self.check_lchmod_link(posix.lchmod, target, link) - - def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): - st = os.stat(target_file) - self.assertHasAttr(st, 'st_flags') - - # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. - flags = st.st_flags | stat.UF_IMMUTABLE - try: - chflags_func(target_file, flags, **kwargs) - except OSError as err: - if err.errno != errno.EOPNOTSUPP: - raise - msg = 'chflag UF_IMMUTABLE not supported by underlying fs' - self.skipTest(msg) - - try: - new_st = os.stat(target_file) - self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags) - try: - fd = open(target_file, 'w+') - except OSError as e: - self.assertEqual(e.errno, errno.EPERM) - finally: - posix.chflags(target_file, st.st_flags) - - @unittest.skipUnless(hasattr(posix, 'chflags'), 'test needs os.chflags()') - def test_chflags(self): - self._test_chflags_regular_file(posix.chflags, os_helper.TESTFN) - - @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()') - def test_lchflags_regular_file(self): - self._test_chflags_regular_file(posix.lchflags, os_helper.TESTFN) - self._test_chflags_regular_file(posix.chflags, os_helper.TESTFN, - follow_symlinks=False) - - @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()') - def test_lchflags_symlink(self): - testfn_st = os.stat(os_helper.TESTFN) - - self.assertHasAttr(testfn_st, 'st_flags') - - self.addCleanup(os_helper.unlink, _DUMMY_SYMLINK) - os.symlink(os_helper.TESTFN, _DUMMY_SYMLINK) - dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) - - def chflags_nofollow(path, flags): - return posix.chflags(path, flags, follow_symlinks=False) - - for fn in (posix.lchflags, chflags_nofollow): - # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. - flags = dummy_symlink_st.st_flags | stat.UF_IMMUTABLE - try: - fn(_DUMMY_SYMLINK, flags) - except OSError as err: - if err.errno != errno.EOPNOTSUPP: - raise - msg = 'chflag UF_IMMUTABLE not supported by underlying fs' - self.skipTest(msg) - try: - new_testfn_st = os.stat(os_helper.TESTFN) - new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) - - self.assertEqual(testfn_st.st_flags, new_testfn_st.st_flags) - self.assertEqual(dummy_symlink_st.st_flags | stat.UF_IMMUTABLE, - new_dummy_symlink_st.st_flags) - finally: - fn(_DUMMY_SYMLINK, dummy_symlink_st.st_flags) - - def test_environ(self): - if os.name == "nt": - item_type = str - else: - item_type = bytes - for k, v in posix.environ.items(): - self.assertEqual(type(k), item_type) - self.assertEqual(type(v), item_type) - - def test_putenv(self): - with self.assertRaises(ValueError): - os.putenv('FRUIT\0VEGETABLE', 'cabbage') - with self.assertRaises(ValueError): - os.putenv('FRUIT', 'orange\0VEGETABLE=cabbage') - with self.assertRaises(ValueError): - os.putenv('FRUIT=ORANGE', 'lemon') - if os.name == 'posix': - with self.assertRaises(ValueError): - os.putenv(b'FRUIT\0VEGETABLE', b'cabbage') - with self.assertRaises(ValueError): - os.putenv(b'FRUIT', b'orange\0VEGETABLE=cabbage') - with self.assertRaises(ValueError): - os.putenv(b'FRUIT=ORANGE', b'lemon') - - @unittest.skipUnless(hasattr(posix, 'getcwd'), 'test needs posix.getcwd()') - def test_getcwd_long_pathnames(self): - dirname = 'getcwd-test-directory-0123456789abcdef-01234567890abcdef' - curdir = os.getcwd() - base_path = os.path.abspath(os_helper.TESTFN) + '.getcwd' - - try: - os.mkdir(base_path) - os.chdir(base_path) - except: - # Just returning nothing instead of the SkipTest exception, because - # the test results in Error in that case. Is that ok? - # raise unittest.SkipTest("cannot create directory for testing") - return - - def _create_and_do_getcwd(dirname, current_path_length = 0): - try: - os.mkdir(dirname) - except: - raise unittest.SkipTest("mkdir cannot create directory sufficiently deep for getcwd test") - - os.chdir(dirname) - try: - os.getcwd() - if current_path_length < 1027: - _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) - finally: - os.chdir('..') - os.rmdir(dirname) - - _create_and_do_getcwd(dirname) - - finally: - os.chdir(curdir) - os_helper.rmtree(base_path) - - @unittest.skipUnless(hasattr(posix, 'getgrouplist'), "test needs posix.getgrouplist()") - @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()") - @unittest.skipUnless(hasattr(os, 'getuid'), "test needs os.getuid()") - def test_getgrouplist(self): - user = pwd.getpwuid(os.getuid())[0] - group = pwd.getpwuid(os.getuid())[3] - self.assertIn(group, posix.getgrouplist(user, group)) - - - @unittest.skipUnless(hasattr(os, 'getegid'), "test needs os.getegid()") - @unittest.skipUnless(hasattr(os, 'popen'), "test needs os.popen()") - @support.requires_subprocess() - def test_getgroups(self): - with os.popen('id -G 2>/dev/null') as idg: - groups = idg.read().strip() - ret = idg.close() - - try: - idg_groups = set(int(g) for g in groups.split()) - except ValueError: - idg_groups = set() - if ret is not None or not idg_groups: - raise unittest.SkipTest("need working 'id -G'") - - # Issues 16698: OS X ABIs prior to 10.6 have limits on getgroups() - if sys.platform == 'darwin': - import sysconfig - dt = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') or '10.3' - if tuple(int(n) for n in dt.split('.')[0:2]) < (10, 6): - raise unittest.SkipTest("getgroups(2) is broken prior to 10.6") - - # 'id -G' and 'os.getgroups()' should return the same - # groups, ignoring order, duplicates, and the effective gid. - # #10822/#26944 - It is implementation defined whether - # posix.getgroups() includes the effective gid. - symdiff = idg_groups.symmetric_difference(posix.getgroups()) - self.assertTrue(not symdiff or symdiff == {posix.getegid()}) - - @unittest.skipUnless(hasattr(signal, 'SIGCHLD'), 'CLD_XXXX be placed in si_code for a SIGCHLD signal') - @unittest.skipUnless(hasattr(os, 'waitid_result'), "test needs os.waitid_result") - def test_cld_xxxx_constants(self): - os.CLD_EXITED - os.CLD_KILLED - os.CLD_DUMPED - os.CLD_TRAPPED - os.CLD_STOPPED - os.CLD_CONTINUED - - requires_sched_h = unittest.skipUnless(hasattr(posix, 'sched_yield'), - "don't have scheduling support") - requires_sched_affinity = unittest.skipUnless(hasattr(posix, 'sched_setaffinity'), - "don't have sched affinity support") - - @requires_sched_h - def test_sched_yield(self): - # This has no error conditions (at least on Linux). - posix.sched_yield() - - @requires_sched_h - @unittest.skipUnless(hasattr(posix, 'sched_get_priority_max'), - "requires sched_get_priority_max()") - def test_sched_priority(self): - # Round-robin usually has interesting priorities. - pol = posix.SCHED_RR - lo = posix.sched_get_priority_min(pol) - hi = posix.sched_get_priority_max(pol) - self.assertIsInstance(lo, int) - self.assertIsInstance(hi, int) - self.assertGreaterEqual(hi, lo) - # Apple platforms return 15 without checking the argument. - if not is_apple: - self.assertRaises(OSError, posix.sched_get_priority_min, -23) - self.assertRaises(OSError, posix.sched_get_priority_max, -23) - - @requires_sched - def test_get_and_set_scheduler_and_param(self): - possible_schedulers = [sched for name, sched in posix.__dict__.items() - if name.startswith("SCHED_")] - mine = posix.sched_getscheduler(0) - self.assertIn(mine, possible_schedulers) - try: - parent = posix.sched_getscheduler(os.getppid()) - except PermissionError: - # POSIX specifies EPERM, but Android returns EACCES. Both errno - # values are mapped to PermissionError. - pass - else: - self.assertIn(parent, possible_schedulers) - self.assertRaises(OSError, posix.sched_getscheduler, -1) - self.assertRaises(OSError, posix.sched_getparam, -1) - param = posix.sched_getparam(0) - self.assertIsInstance(param.sched_priority, int) - - # POSIX states that calling sched_setparam() or sched_setscheduler() on - # a process with a scheduling policy other than SCHED_FIFO or SCHED_RR - # is implementation-defined: NetBSD and FreeBSD can return EINVAL. - if not sys.platform.startswith(('freebsd', 'netbsd')): - try: - posix.sched_setscheduler(0, mine, param) - posix.sched_setparam(0, param) - except PermissionError: - pass - self.assertRaises(OSError, posix.sched_setparam, -1, param) - - self.assertRaises(OSError, posix.sched_setscheduler, -1, mine, param) - self.assertRaises(TypeError, posix.sched_setscheduler, 0, mine, None) - self.assertRaises(TypeError, posix.sched_setparam, 0, 43) - param = posix.sched_param(None) - self.assertRaises(TypeError, posix.sched_setparam, 0, param) - large = 214748364700 - param = posix.sched_param(large) - self.assertRaises(OverflowError, posix.sched_setparam, 0, param) - param = posix.sched_param(sched_priority=-large) - self.assertRaises(OverflowError, posix.sched_setparam, 0, param) - - @requires_sched - def test_sched_param(self): - param = posix.sched_param(1) - for proto in range(pickle.HIGHEST_PROTOCOL+1): - newparam = pickle.loads(pickle.dumps(param, proto)) - self.assertEqual(newparam, param) - newparam = copy.copy(param) - self.assertIsNot(newparam, param) - self.assertEqual(newparam, param) - newparam = copy.deepcopy(param) - self.assertIsNot(newparam, param) - self.assertEqual(newparam, param) - newparam = copy.replace(param) - self.assertIsNot(newparam, param) - self.assertEqual(newparam, param) - newparam = copy.replace(param, sched_priority=0) - self.assertNotEqual(newparam, param) - self.assertEqual(newparam.sched_priority, 0) - - @unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function") - def test_sched_rr_get_interval(self): - try: - interval = posix.sched_rr_get_interval(0) - except OSError as e: - # This likely means that sched_rr_get_interval is only valid for - # processes with the SCHED_RR scheduler in effect. - if e.errno != errno.EINVAL: - raise - self.skipTest("only works on SCHED_RR processes") - self.assertIsInstance(interval, float) - # Reasonable constraints, I think. - self.assertGreaterEqual(interval, 0.) - self.assertLess(interval, 1.) - - @requires_sched_affinity - def test_sched_getaffinity(self): - mask = posix.sched_getaffinity(0) - self.assertIsInstance(mask, set) - self.assertGreaterEqual(len(mask), 1) - if not sys.platform.startswith("freebsd"): - # bpo-47205: does not raise OSError on FreeBSD - self.assertRaises(OSError, posix.sched_getaffinity, -1) - for cpu in mask: - self.assertIsInstance(cpu, int) - self.assertGreaterEqual(cpu, 0) - self.assertLess(cpu, 1 << 32) - - @requires_sched_affinity - def test_sched_setaffinity(self): - mask = posix.sched_getaffinity(0) - self.addCleanup(posix.sched_setaffinity, 0, list(mask)) - - if len(mask) > 1: - # Empty masks are forbidden - mask.pop() - posix.sched_setaffinity(0, mask) - self.assertEqual(posix.sched_getaffinity(0), mask) - - try: - posix.sched_setaffinity(0, []) - # gh-117061: On RHEL9, sched_setaffinity(0, []) does not fail - except OSError: - # sched_setaffinity() manual page documents EINVAL error - # when the mask is empty. - pass - - self.assertRaises(ValueError, posix.sched_setaffinity, 0, [-10]) - self.assertRaises(ValueError, posix.sched_setaffinity, 0, map(int, "0X")) - self.assertRaises(OverflowError, posix.sched_setaffinity, 0, [1<<128]) - if not sys.platform.startswith("freebsd"): - # bpo-47205: does not raise OSError on FreeBSD - self.assertRaises(OSError, posix.sched_setaffinity, -1, mask) - - @unittest.skipIf(support.is_wasi, "No dynamic linking on WASI") - @unittest.skipUnless(os.name == 'posix', "POSIX-only test") - def test_rtld_constants(self): - # check presence of major RTLD_* constants - posix.RTLD_LAZY - posix.RTLD_NOW - posix.RTLD_GLOBAL - posix.RTLD_LOCAL - - @unittest.skipUnless(hasattr(os, 'SEEK_HOLE'), - "test needs an OS that reports file holes") - def test_fs_holes(self): - # Even if the filesystem doesn't report holes, - # if the OS supports it the SEEK_* constants - # will be defined and will have a consistent - # behaviour: - # os.SEEK_DATA = current position - # os.SEEK_HOLE = end of file position - with open(os_helper.TESTFN, 'r+b') as fp: - fp.write(b"hello") - fp.flush() - size = fp.tell() - fno = fp.fileno() - try : - for i in range(size): - self.assertEqual(i, os.lseek(fno, i, os.SEEK_DATA)) - self.assertLessEqual(size, os.lseek(fno, i, os.SEEK_HOLE)) - self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_DATA) - self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_HOLE) - except OSError : - # Some OSs claim to support SEEK_HOLE/SEEK_DATA - # but it is not true. - # For instance: - # http://lists.freebsd.org/pipermail/freebsd-amd64/2012-January/014332.html - raise unittest.SkipTest("OSError raised!") - - def test_path_error2(self): - """ - Test functions that call path_error2(), providing two filenames in their exceptions. - """ - for name in ("rename", "replace", "link"): - function = getattr(os, name, None) - if function is None: - continue - - for dst in ("noodly2", os_helper.TESTFN): - try: - function('doesnotexistfilename', dst) - except OSError as e: - self.assertIn("'doesnotexistfilename' -> '{}'".format(dst), str(e)) - break - else: - self.fail("No valid path_error2() test for os." + name) - - def test_path_with_null_character(self): - fn = os_helper.TESTFN - fn_with_NUL = fn + '\0' - self.addCleanup(os_helper.unlink, fn) - os_helper.unlink(fn) - fd = None - try: - with self.assertRaises(ValueError): - fd = os.open(fn_with_NUL, os.O_WRONLY | os.O_CREAT) # raises - finally: - if fd is not None: - os.close(fd) - self.assertFalse(os.path.exists(fn)) - self.assertRaises(ValueError, os.mkdir, fn_with_NUL) - self.assertFalse(os.path.exists(fn)) - open(fn, 'wb').close() - self.assertRaises(ValueError, os.stat, fn_with_NUL) - - def test_path_with_null_byte(self): - fn = os.fsencode(os_helper.TESTFN) - fn_with_NUL = fn + b'\0' - self.addCleanup(os_helper.unlink, fn) - os_helper.unlink(fn) - fd = None - try: - with self.assertRaises(ValueError): - fd = os.open(fn_with_NUL, os.O_WRONLY | os.O_CREAT) # raises - finally: - if fd is not None: - os.close(fd) - self.assertFalse(os.path.exists(fn)) - self.assertRaises(ValueError, os.mkdir, fn_with_NUL) - self.assertFalse(os.path.exists(fn)) - open(fn, 'wb').close() - self.assertRaises(ValueError, os.stat, fn_with_NUL) - - @unittest.skipUnless(hasattr(os, "pidfd_open"), "pidfd_open unavailable") - def test_pidfd_open(self): - with self.assertRaises(OSError) as cm: - os.pidfd_open(-1) - if cm.exception.errno == errno.ENOSYS: - self.skipTest("system does not support pidfd_open") - if isinstance(cm.exception, PermissionError): - self.skipTest(f"pidfd_open syscall blocked: {cm.exception!r}") - self.assertEqual(cm.exception.errno, errno.EINVAL) - os.close(os.pidfd_open(os.getpid(), 0)) - - @os_helper.skip_unless_hardlink - @os_helper.skip_unless_symlink - def test_link_follow_symlinks(self): - default_follow = sys.platform.startswith( - ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5')) - default_no_follow = sys.platform.startswith(('win32', 'linux')) - orig = os_helper.TESTFN - symlink = orig + 'symlink' - posix.symlink(orig, symlink) - self.addCleanup(os_helper.unlink, symlink) - - with self.subTest('no follow_symlinks'): - # no follow_symlinks -> platform depending - link = orig + 'link' - posix.link(symlink, link) - self.addCleanup(os_helper.unlink, link) - if os.link in os.supports_follow_symlinks or default_follow: - self.assertEqual(posix.lstat(link), posix.lstat(orig)) - elif default_no_follow: - self.assertEqual(posix.lstat(link), posix.lstat(symlink)) - - with self.subTest('follow_symlinks=False'): - # follow_symlinks=False -> duplicate the symlink itself - link = orig + 'link_nofollow' - try: - posix.link(symlink, link, follow_symlinks=False) - except NotImplementedError: - if os.link in os.supports_follow_symlinks or default_no_follow: - raise - else: - self.addCleanup(os_helper.unlink, link) - self.assertEqual(posix.lstat(link), posix.lstat(symlink)) - - with self.subTest('follow_symlinks=True'): - # follow_symlinks=True -> duplicate the target file - link = orig + 'link_following' - try: - posix.link(symlink, link, follow_symlinks=True) - except NotImplementedError: - if os.link in os.supports_follow_symlinks or default_follow: - raise - else: - self.addCleanup(os_helper.unlink, link) - self.assertEqual(posix.lstat(link), posix.lstat(orig)) - - -# tests for the posix *at functions follow -class TestPosixDirFd(unittest.TestCase): - count = 0 - - @contextmanager - def prepare(self): - TestPosixDirFd.count += 1 - name = f'{os_helper.TESTFN}_{self.count}' - base_dir = f'{os_helper.TESTFN}_{self.count}base' - posix.mkdir(base_dir) - self.addCleanup(posix.rmdir, base_dir) - fullname = os.path.join(base_dir, name) - assert not os.path.exists(fullname) - with os_helper.open_dir_fd(base_dir) as dir_fd: - yield (dir_fd, name, fullname) - - @contextmanager - def prepare_file(self): - with self.prepare() as (dir_fd, name, fullname): - os_helper.create_empty_file(fullname) - self.addCleanup(posix.unlink, fullname) - yield (dir_fd, name, fullname) - - @unittest.skipUnless(os.access in os.supports_dir_fd, "test needs dir_fd support for os.access()") - def test_access_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname): - self.assertTrue(posix.access(name, os.R_OK, dir_fd=dir_fd)) - - @unittest.skipUnless(os.chmod in os.supports_dir_fd, "test needs dir_fd support in os.chmod()") - def test_chmod_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname): - posix.chmod(fullname, stat.S_IRUSR) - posix.chmod(name, stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd) - s = posix.stat(fullname) - self.assertEqual(s.st_mode & stat.S_IRWXU, - stat.S_IRUSR | stat.S_IWUSR) - - @unittest.skipUnless(hasattr(os, 'chown') and (os.chown in os.supports_dir_fd), - "test needs dir_fd support in os.chown()") - @unittest.skipIf(support.is_emscripten, "getgid() is a stub") - def test_chown_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname): - posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) - - @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") - def test_stat_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - with open(fullname, 'w') as outfile: - outfile.write("testline\n") - self.addCleanup(posix.unlink, fullname) - - s1 = posix.stat(fullname) - s2 = posix.stat(name, dir_fd=dir_fd) - self.assertEqual(s1, s2) - s2 = posix.stat(fullname, dir_fd=None) - self.assertEqual(s1, s2) - - self.assertRaisesRegex(TypeError, 'should be integer or None, not', - posix.stat, name, dir_fd=posix.getcwd()) - self.assertRaisesRegex(TypeError, 'should be integer or None, not', - posix.stat, name, dir_fd=float(dir_fd)) - self.assertRaises(OverflowError, - posix.stat, name, dir_fd=10**20) - - for fd in False, True: - with self.assertWarnsRegex(RuntimeWarning, - 'bool is used as a file descriptor') as cm: - with self.assertRaises(OSError): - posix.stat('nonexisting', dir_fd=fd) - self.assertEqual(cm.filename, __file__) - - @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") - def test_utime_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname): - now = time.time() - posix.utime(name, None, dir_fd=dir_fd) - posix.utime(name, dir_fd=dir_fd) - self.assertRaises(TypeError, posix.utime, name, - now, dir_fd=dir_fd) - self.assertRaises(TypeError, posix.utime, name, - (None, None), dir_fd=dir_fd) - self.assertRaises(TypeError, posix.utime, name, - (now, None), dir_fd=dir_fd) - self.assertRaises(TypeError, posix.utime, name, - (None, now), dir_fd=dir_fd) - self.assertRaises(TypeError, posix.utime, name, - (now, "x"), dir_fd=dir_fd) - posix.utime(name, (int(now), int(now)), dir_fd=dir_fd) - posix.utime(name, (now, now), dir_fd=dir_fd) - posix.utime(name, - (int(now), int((now - int(now)) * 1e9)), dir_fd=dir_fd) - posix.utime(name, dir_fd=dir_fd, - times=(int(now), int((now - int(now)) * 1e9))) - - # try dir_fd and follow_symlinks together - if os.utime in os.supports_follow_symlinks: - try: - posix.utime(name, follow_symlinks=False, dir_fd=dir_fd) - except ValueError: - # whoops! using both together not supported on this platform. - pass - - @unittest.skipIf( - support.is_wasi, - "WASI: symlink following on path_link is not supported" - ) - @unittest.skipUnless( - hasattr(os, "link") and os.link in os.supports_dir_fd, - "test needs dir_fd support in os.link()" - ) - def test_link_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname), \ - self.prepare() as (dir_fd2, linkname, fulllinkname): - try: - posix.link(name, linkname, src_dir_fd=dir_fd, dst_dir_fd=dir_fd2) - except PermissionError as e: - self.skipTest('posix.link(): %s' % e) - self.addCleanup(posix.unlink, fulllinkname) - # should have same inodes - self.assertEqual(posix.stat(fullname)[1], - posix.stat(fulllinkname)[1]) - - @unittest.skipUnless(os.mkdir in os.supports_dir_fd, "test needs dir_fd support in os.mkdir()") - def test_mkdir_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - posix.mkdir(name, dir_fd=dir_fd) - self.addCleanup(posix.rmdir, fullname) - posix.stat(fullname) # should not raise exception - - @unittest.skipUnless(hasattr(os, 'mknod') - and (os.mknod in os.supports_dir_fd) - and hasattr(stat, 'S_IFIFO'), - "test requires both stat.S_IFIFO and dir_fd support for os.mknod()") - def test_mknod_dir_fd(self): - # Test using mknodat() to create a FIFO (the only use specified - # by POSIX). - with self.prepare() as (dir_fd, name, fullname): - mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR - try: - posix.mknod(name, mode, 0, dir_fd=dir_fd) - except OSError as e: - # Some old systems don't allow unprivileged users to use - # mknod(), or only support creating device nodes. - self.assertIn(e.errno, (errno.EPERM, errno.EINVAL, errno.EACCES)) - else: - self.addCleanup(posix.unlink, fullname) - self.assertTrue(stat.S_ISFIFO(posix.stat(fullname).st_mode)) - - @unittest.skipUnless(os.open in os.supports_dir_fd, "test needs dir_fd support in os.open()") - def test_open_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - with open(fullname, 'wb') as outfile: - outfile.write(b"testline\n") - self.addCleanup(posix.unlink, fullname) - fd = posix.open(name, posix.O_RDONLY, dir_fd=dir_fd) - try: - res = posix.read(fd, 9) - self.assertEqual(b"testline\n", res) - finally: - posix.close(fd) - - @unittest.skipUnless(hasattr(os, 'readlink') and (os.readlink in os.supports_dir_fd), - "test needs dir_fd support in os.readlink()") - def test_readlink_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - os.symlink('symlink', fullname) - self.addCleanup(posix.unlink, fullname) - self.assertEqual(posix.readlink(name, dir_fd=dir_fd), 'symlink') - - @unittest.skipUnless(os.rename in os.supports_dir_fd, "test needs dir_fd support in os.rename()") - def test_rename_dir_fd(self): - with self.prepare_file() as (dir_fd, name, fullname), \ - self.prepare() as (dir_fd2, name2, fullname2): - posix.rename(name, name2, - src_dir_fd=dir_fd, dst_dir_fd=dir_fd2) - posix.stat(fullname2) # should not raise exception - posix.rename(fullname2, fullname) - - @unittest.skipUnless(os.symlink in os.supports_dir_fd, "test needs dir_fd support in os.symlink()") - def test_symlink_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - posix.symlink('symlink', name, dir_fd=dir_fd) - self.addCleanup(posix.unlink, fullname) - self.assertEqual(posix.readlink(fullname), 'symlink') - - @unittest.skipUnless(os.unlink in os.supports_dir_fd, "test needs dir_fd support in os.unlink()") - def test_unlink_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - os_helper.create_empty_file(fullname) - posix.stat(fullname) # should not raise exception - try: - posix.unlink(name, dir_fd=dir_fd) - self.assertRaises(OSError, posix.stat, fullname) - except: - self.addCleanup(posix.unlink, fullname) - raise - - @unittest.skipUnless(hasattr(os, 'mkfifo') and os.mkfifo in os.supports_dir_fd, "test needs dir_fd support in os.mkfifo()") - def test_mkfifo_dir_fd(self): - with self.prepare() as (dir_fd, name, fullname): - try: - posix.mkfifo(name, stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd) - except PermissionError as e: - self.skipTest('posix.mkfifo(): %s' % e) - self.addCleanup(posix.unlink, fullname) - self.assertTrue(stat.S_ISFIFO(posix.stat(fullname).st_mode)) - - -class PosixGroupsTester(unittest.TestCase): - - def setUp(self): - if posix.getuid() != 0: - raise unittest.SkipTest("not enough privileges") - if not hasattr(posix, 'getgroups'): - raise unittest.SkipTest("need posix.getgroups") - if sys.platform == 'darwin': - raise unittest.SkipTest("getgroups(2) is broken on OSX") - self.saved_groups = posix.getgroups() - - def tearDown(self): - if hasattr(posix, 'setgroups'): - posix.setgroups(self.saved_groups) - elif hasattr(posix, 'initgroups'): - name = pwd.getpwuid(posix.getuid()).pw_name - posix.initgroups(name, self.saved_groups[0]) - - @unittest.skipUnless(hasattr(posix, 'initgroups'), - "test needs posix.initgroups()") - def test_initgroups(self): - # find missing group - - g = max(self.saved_groups or [0]) + 1 - name = pwd.getpwuid(posix.getuid()).pw_name - posix.initgroups(name, g) - self.assertIn(g, posix.getgroups()) - - @unittest.skipUnless(hasattr(posix, 'setgroups'), - "test needs posix.setgroups()") - def test_setgroups(self): - for groups in [[0], list(range(16))]: - posix.setgroups(groups) - self.assertListEqual(groups, posix.getgroups()) - - -class _PosixSpawnMixin: - # Program which does nothing and exits with status 0 (success) - NOOP_PROGRAM = (sys.executable, '-I', '-S', '-c', 'pass') - spawn_func = None - - def python_args(self, *args): - # Disable site module to avoid side effects. For example, - # on Fedora 28, if the HOME environment variable is not set, - # site._getuserbase() calls pwd.getpwuid() which opens - # /var/lib/sss/mc/passwd but then leaves the file open which makes - # test_close_file() to fail. - return (sys.executable, '-I', '-S', *args) - - def test_returns_pid(self): - pidfile = os_helper.TESTFN - self.addCleanup(os_helper.unlink, pidfile) - script = f"""if 1: - import os - with open({pidfile!r}, "w") as pidfile: - pidfile.write(str(os.getpid())) - """ - args = self.python_args('-c', script) - pid = self.spawn_func(args[0], args, os.environ) - support.wait_process(pid, exitcode=0) - with open(pidfile, encoding="utf-8") as f: - self.assertEqual(f.read(), str(pid)) - - def test_no_such_executable(self): - no_such_executable = 'no_such_executable' - try: - pid = self.spawn_func(no_such_executable, - [no_such_executable], - os.environ) - # bpo-35794: PermissionError can be raised if there are - # directories in the $PATH that are not accessible. - except (FileNotFoundError, PermissionError) as exc: - self.assertEqual(exc.filename, no_such_executable) - else: - pid2, status = os.waitpid(pid, 0) - self.assertEqual(pid2, pid) - self.assertNotEqual(status, 0) - - def test_specify_environment(self): - envfile = os_helper.TESTFN - self.addCleanup(os_helper.unlink, envfile) - script = f"""if 1: - import os - with open({envfile!r}, "w", encoding="utf-8") as envfile: - envfile.write(os.environ['foo']) - """ - args = self.python_args('-c', script) - pid = self.spawn_func(args[0], args, - {**os.environ, 'foo': 'bar'}) - support.wait_process(pid, exitcode=0) - with open(envfile, encoding="utf-8") as f: - self.assertEqual(f.read(), 'bar') - - def test_none_file_actions(self): - pid = self.spawn_func( - self.NOOP_PROGRAM[0], - self.NOOP_PROGRAM, - os.environ, - file_actions=None - ) - support.wait_process(pid, exitcode=0) - - def test_empty_file_actions(self): - pid = self.spawn_func( - self.NOOP_PROGRAM[0], - self.NOOP_PROGRAM, - os.environ, - file_actions=[] - ) - support.wait_process(pid, exitcode=0) - - def test_resetids_explicit_default(self): - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', 'pass'], - os.environ, - resetids=False - ) - support.wait_process(pid, exitcode=0) - - def test_resetids(self): - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', 'pass'], - os.environ, - resetids=True - ) - support.wait_process(pid, exitcode=0) - - def test_setpgroup(self): - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', 'pass'], - os.environ, - setpgroup=os.getpgrp() - ) - support.wait_process(pid, exitcode=0) - - def test_setpgroup_wrong_type(self): - with self.assertRaises(TypeError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setpgroup="023") - - @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), - 'need signal.pthread_sigmask()') - def test_setsigmask(self): - code = textwrap.dedent("""\ - import signal - signal.raise_signal(signal.SIGUSR1)""") - - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', code], - os.environ, - setsigmask=[signal.SIGUSR1] - ) - support.wait_process(pid, exitcode=0) - - def test_setsigmask_wrong_type(self): - with self.assertRaises(TypeError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigmask=34) - with self.assertRaises(TypeError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigmask=["j"]) - with self.assertRaises(ValueError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigmask=[signal.NSIG, - signal.NSIG+1]) - - def test_setsid(self): - rfd, wfd = os.pipe() - self.addCleanup(os.close, rfd) - try: - os.set_inheritable(wfd, True) - - code = textwrap.dedent(f""" - import os - fd = {wfd} - sid = os.getsid(0) - os.write(fd, str(sid).encode()) - """) - - try: - pid = self.spawn_func(sys.executable, - [sys.executable, "-c", code], - os.environ, setsid=True) - except NotImplementedError as exc: - self.skipTest(f"setsid is not supported: {exc!r}") - except PermissionError as exc: - self.skipTest(f"setsid failed with: {exc!r}") - finally: - os.close(wfd) - - support.wait_process(pid, exitcode=0) - - output = os.read(rfd, 100) - child_sid = int(output) - parent_sid = os.getsid(os.getpid()) - self.assertNotEqual(parent_sid, child_sid) - - @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), - 'need signal.pthread_sigmask()') - def test_setsigdef(self): - original_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN) - code = textwrap.dedent("""\ - import signal - signal.raise_signal(signal.SIGUSR1)""") - try: - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', code], - os.environ, - setsigdef=[signal.SIGUSR1] - ) - finally: - signal.signal(signal.SIGUSR1, original_handler) - - support.wait_process(pid, exitcode=-signal.SIGUSR1) - - def test_setsigdef_wrong_type(self): - with self.assertRaises(TypeError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigdef=34) - with self.assertRaises(TypeError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigdef=["j"]) - with self.assertRaises(ValueError): - self.spawn_func(sys.executable, - [sys.executable, "-c", "pass"], - os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) - - @requires_sched - @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), - "bpo-34685: test can fail on BSD") - def test_setscheduler_only_param(self): - policy = os.sched_getscheduler(0) - priority = os.sched_get_priority_min(policy) - code = textwrap.dedent(f"""\ - import os, sys - if os.sched_getscheduler(0) != {policy}: - sys.exit(101) - if os.sched_getparam(0).sched_priority != {priority}: - sys.exit(102)""") - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', code], - os.environ, - scheduler=(None, os.sched_param(priority)) - ) - support.wait_process(pid, exitcode=0) - - @requires_sched - @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), - "bpo-34685: test can fail on BSD") - @unittest.skipIf(platform.libc_ver()[0] == 'glibc' and - os.sched_getscheduler(0) in [ - os.SCHED_BATCH, - os.SCHED_IDLE, - os.SCHED_DEADLINE], - "Skip test due to glibc posix_spawn policy") - def test_setscheduler_with_policy(self): - policy = os.sched_getscheduler(0) - priority = os.sched_get_priority_min(policy) - code = textwrap.dedent(f"""\ - import os, sys - if os.sched_getscheduler(0) != {policy}: - sys.exit(101) - if os.sched_getparam(0).sched_priority != {priority}: - sys.exit(102)""") - pid = self.spawn_func( - sys.executable, - [sys.executable, '-c', code], - os.environ, - scheduler=(policy, os.sched_param(priority)) - ) - support.wait_process(pid, exitcode=0) - - def test_multiple_file_actions(self): - file_actions = [ - (os.POSIX_SPAWN_OPEN, 3, os.path.realpath(__file__), os.O_RDONLY, 0), - (os.POSIX_SPAWN_CLOSE, 0), - (os.POSIX_SPAWN_DUP2, 1, 4), - ] - pid = self.spawn_func(self.NOOP_PROGRAM[0], - self.NOOP_PROGRAM, - os.environ, - file_actions=file_actions) - support.wait_process(pid, exitcode=0) - - def test_bad_file_actions(self): - args = self.NOOP_PROGRAM - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[None]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[()]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(None,)]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(12345,)]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(os.POSIX_SPAWN_CLOSE,)]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)]) - with self.assertRaises(TypeError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(os.POSIX_SPAWN_CLOSE, None)]) - with self.assertRaises(ValueError): - self.spawn_func(args[0], args, os.environ, - file_actions=[(os.POSIX_SPAWN_OPEN, - 3, __file__ + '\0', - os.O_RDONLY, 0)]) - - def test_open_file(self): - outfile = os_helper.TESTFN - self.addCleanup(os_helper.unlink, outfile) - script = """if 1: - import sys - sys.stdout.write("hello") - """ - file_actions = [ - (os.POSIX_SPAWN_OPEN, 1, outfile, - os.O_WRONLY | os.O_CREAT | os.O_TRUNC, - stat.S_IRUSR | stat.S_IWUSR), - ] - args = self.python_args('-c', script) - pid = self.spawn_func(args[0], args, os.environ, - file_actions=file_actions) - - support.wait_process(pid, exitcode=0) - with open(outfile, encoding="utf-8") as f: - self.assertEqual(f.read(), 'hello') - - def test_close_file(self): - closefile = os_helper.TESTFN - self.addCleanup(os_helper.unlink, closefile) - script = f"""if 1: - import os - try: - os.fstat(0) - except OSError as e: - with open({closefile!r}, 'w', encoding='utf-8') as closefile: - closefile.write('is closed %d' % e.errno) - """ - args = self.python_args('-c', script) - pid = self.spawn_func(args[0], args, os.environ, - file_actions=[(os.POSIX_SPAWN_CLOSE, 0)]) - - support.wait_process(pid, exitcode=0) - with open(closefile, encoding="utf-8") as f: - self.assertEqual(f.read(), 'is closed %d' % errno.EBADF) - - def test_dup2(self): - dupfile = os_helper.TESTFN - self.addCleanup(os_helper.unlink, dupfile) - script = """if 1: - import sys - sys.stdout.write("hello") - """ - with open(dupfile, "wb") as childfile: - file_actions = [ - (os.POSIX_SPAWN_DUP2, childfile.fileno(), 1), - ] - args = self.python_args('-c', script) - pid = self.spawn_func(args[0], args, os.environ, - file_actions=file_actions) - support.wait_process(pid, exitcode=0) - with open(dupfile, encoding="utf-8") as f: - self.assertEqual(f.read(), 'hello') - - -@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") -@support.requires_subprocess() -class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): - spawn_func = getattr(posix, 'posix_spawn', None) - - -@unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") -@support.requires_subprocess() -class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): - spawn_func = getattr(posix, 'posix_spawnp', None) - - @os_helper.skip_unless_symlink - def test_posix_spawnp(self): - # Use a symlink to create a program in its own temporary directory - temp_dir = tempfile.mkdtemp() - self.addCleanup(os_helper.rmtree, temp_dir) - - program = 'posix_spawnp_test_program.exe' - program_fullpath = os.path.join(temp_dir, program) - os.symlink(sys.executable, program_fullpath) - - try: - path = os.pathsep.join((temp_dir, os.environ['PATH'])) - except KeyError: - path = temp_dir # PATH is not set - - spawn_args = (program, '-I', '-S', '-c', 'pass') - code = textwrap.dedent(""" - import os - from test import support - - args = %a - pid = os.posix_spawnp(args[0], args, os.environ) - - support.wait_process(pid, exitcode=0) - """ % (spawn_args,)) - - # Use a subprocess to test os.posix_spawnp() with a modified PATH - # environment variable: posix_spawnp() uses the current environment - # to locate the program, not its environment argument. - args = ('-c', code) - assert_python_ok(*args, PATH=path) - - -@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS") -class TestPosixWeaklinking(unittest.TestCase): - # These test cases verify that weak linking support on macOS works - # as expected. These cases only test new behaviour introduced by weak linking, - # regular behaviour is tested by the normal test cases. - # - # See the section on Weak Linking in Mac/README.txt for more information. - def setUp(self): - import sysconfig - import platform - - config_vars = sysconfig.get_config_vars() - self.available = { nm for nm in config_vars if nm.startswith("HAVE_") and config_vars[nm] } - self.mac_ver = tuple(int(part) for part in platform.mac_ver()[0].split(".")) - - def _verify_available(self, name): - if name not in self.available: - raise unittest.SkipTest(f"{name} not weak-linked") - - def test_pwritev(self): - self._verify_available("HAVE_PWRITEV") - if self.mac_ver >= (10, 16): - self.assertHasAttr(os, "pwritev") - self.assertHasAttr(os, "preadv") - - else: - self.assertNotHasAttr(os, "pwritev") - self.assertNotHasAttr(os, "preadv") - - def test_stat(self): - self._verify_available("HAVE_FSTATAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_FSTATAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_FSTATAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.stat("file", dir_fd=0) - - def test_ptsname_r(self): - self._verify_available("HAVE_PTSNAME_R") - if self.mac_ver >= (10, 13, 4): - self.assertIn("HAVE_PTSNAME_R", posix._have_functions) - else: - self.assertNotIn("HAVE_PTSNAME_R", posix._have_functions) - - def test_access(self): - self._verify_available("HAVE_FACCESSAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_FACCESSAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_FACCESSAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.access("file", os.R_OK, dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "follow_symlinks unavailable"): - os.access("file", os.R_OK, follow_symlinks=False) - - with self.assertRaisesRegex(NotImplementedError, "effective_ids unavailable"): - os.access("file", os.R_OK, effective_ids=True) - - def test_chmod(self): - self._verify_available("HAVE_FCHMODAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_FCHMODAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_FCHMODAT", posix._have_functions) - self.assertIn("HAVE_LCHMOD", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.chmod("file", 0o644, dir_fd=0) - - def test_chown(self): - self._verify_available("HAVE_FCHOWNAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_FCHOWNAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_FCHOWNAT", posix._have_functions) - self.assertIn("HAVE_LCHOWN", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.chown("file", 0, 0, dir_fd=0) - - def test_link(self): - self._verify_available("HAVE_LINKAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_LINKAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_LINKAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): - os.link("source", "target", src_dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "dst_dir_fd unavailable"): - os.link("source", "target", dst_dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): - os.link("source", "target", src_dir_fd=0, dst_dir_fd=0) - - # issue 41355: !HAVE_LINKAT code path ignores the follow_symlinks flag - with os_helper.temp_dir() as base_path: - link_path = os.path.join(base_path, "link") - target_path = os.path.join(base_path, "target") - source_path = os.path.join(base_path, "source") - - with open(source_path, "w") as fp: - fp.write("data") - - os.symlink("target", link_path) - - # Calling os.link should fail in the link(2) call, and - # should not reject *follow_symlinks* (to match the - # behaviour you'd get when building on a platform without - # linkat) - with self.assertRaises(FileExistsError): - os.link(source_path, link_path, follow_symlinks=True) - - with self.assertRaises(FileExistsError): - os.link(source_path, link_path, follow_symlinks=False) - - - def test_listdir_scandir(self): - self._verify_available("HAVE_FDOPENDIR") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_FDOPENDIR", posix._have_functions) - - else: - self.assertNotIn("HAVE_FDOPENDIR", posix._have_functions) - - with self.assertRaisesRegex(TypeError, "listdir: path should be string, bytes, os.PathLike or None, not int"): - os.listdir(0) - - with self.assertRaisesRegex(TypeError, "scandir: path should be string, bytes, os.PathLike or None, not int"): - os.scandir(0) - - def test_mkdir(self): - self._verify_available("HAVE_MKDIRAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_MKDIRAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_MKDIRAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.mkdir("dir", dir_fd=0) - - def test_mkfifo(self): - self._verify_available("HAVE_MKFIFOAT") - if self.mac_ver >= (13, 0): - self.assertIn("HAVE_MKFIFOAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_MKFIFOAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.mkfifo("path", dir_fd=0) - - def test_mknod(self): - self._verify_available("HAVE_MKNODAT") - if self.mac_ver >= (13, 0): - self.assertIn("HAVE_MKNODAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_MKNODAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.mknod("path", dir_fd=0) - - def test_rename_replace(self): - self._verify_available("HAVE_RENAMEAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_RENAMEAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_RENAMEAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): - os.rename("a", "b", src_dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): - os.rename("a", "b", dst_dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): - os.replace("a", "b", src_dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): - os.replace("a", "b", dst_dir_fd=0) - - def test_unlink_rmdir(self): - self._verify_available("HAVE_UNLINKAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_UNLINKAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_UNLINKAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.unlink("path", dir_fd=0) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.rmdir("path", dir_fd=0) - - def test_open(self): - self._verify_available("HAVE_OPENAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_OPENAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_OPENAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.open("path", os.O_RDONLY, dir_fd=0) - - def test_readlink(self): - self._verify_available("HAVE_READLINKAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_READLINKAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_READLINKAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.readlink("path", dir_fd=0) - - def test_symlink(self): - self._verify_available("HAVE_SYMLINKAT") - if self.mac_ver >= (10, 10): - self.assertIn("HAVE_SYMLINKAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_SYMLINKAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.symlink("a", "b", dir_fd=0) - - def test_utime(self): - self._verify_available("HAVE_FUTIMENS") - self._verify_available("HAVE_UTIMENSAT") - if self.mac_ver >= (10, 13): - self.assertIn("HAVE_FUTIMENS", posix._have_functions) - self.assertIn("HAVE_UTIMENSAT", posix._have_functions) - - else: - self.assertNotIn("HAVE_FUTIMENS", posix._have_functions) - self.assertNotIn("HAVE_UTIMENSAT", posix._have_functions) - - with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): - os.utime("path", dir_fd=0) - - -class NamespacesTests(unittest.TestCase): - """Tests for os.unshare() and os.setns().""" - - @unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()') - @unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()') - @unittest.skipUnless(os.path.exists('/proc/self/ns/uts'), 'need /proc/self/ns/uts') - @support.requires_linux_version(3, 0, 0) - def test_unshare_setns(self): - code = """if 1: - import errno - import os - import sys - fd = os.open('/proc/self/ns/uts', os.O_RDONLY) - try: - original = os.readlink('/proc/self/ns/uts') - try: - os.unshare(os.CLONE_NEWUTS) - except OSError as e: - if e.errno == errno.ENOSPC: - # skip test if limit is exceeded - sys.exit() - raise - new = os.readlink('/proc/self/ns/uts') - if original == new: - raise Exception('os.unshare failed') - os.setns(fd, os.CLONE_NEWUTS) - restored = os.readlink('/proc/self/ns/uts') - if original != restored: - raise Exception('os.setns failed') - except PermissionError: - # The calling process did not have the required privileges - # for this operation - pass - except OSError as e: - # Skip the test on these errors: - # - ENOSYS: syscall not available - # - EINVAL: kernel was not configured with the CONFIG_UTS_NS option - # - ENOMEM: not enough memory - if e.errno not in (errno.ENOSYS, errno.EINVAL, errno.ENOMEM): - raise - finally: - os.close(fd) - """ - - assert_python_ok("-c", code) - - -def tearDownModule(): - support.reap_children() - - -if __name__ == '__main__': - unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index eedccc3ffe6a49..6651b093e20c8d 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2682,6 +2682,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_multiprocessing_fork \ test/test_multiprocessing_forkserver \ test/test_multiprocessing_spawn \ + test/test_os \ test/test_pathlib \ test/test_pathlib/support \ test/test_peg_generator \ diff --git a/Misc/NEWS.d/next/Tests/2025-09-25-13-14-41.gh-issue-139322.KLOAds.rst b/Misc/NEWS.d/next/Tests/2025-09-25-13-14-41.gh-issue-139322.KLOAds.rst new file mode 100644 index 00000000000000..b8bc77f1abd965 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2025-09-25-13-14-41.gh-issue-139322.KLOAds.rst @@ -0,0 +1 @@ +Move test_posix into a new test_os package. Patch by Victor Stinner.