summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_os.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_os.py')
-rw-r--r--Lib/test/test_os.py848
1 files changed, 811 insertions, 37 deletions
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index cbf0c16..35aa7fa 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -7,8 +7,21 @@ import errno
import unittest
import warnings
import sys
+import signal
+import subprocess
+import time
import shutil
from test import support
+import contextlib
+import mmap
+import uuid
+import asyncore
+import asynchat
+import socket
+try:
+ import threading
+except ImportError:
+ threading = None
# Detect whether we're on a Linux system that uses the (now outdated
# and unmaintained) linuxthreads threading library. There's an issue
@@ -54,6 +67,7 @@ class FileTests(unittest.TestCase):
os.closerange(first, first + 2)
self.assertRaises(OSError, os.write, first, b"a")
+ @support.cpython_only
def test_rename(self):
path = support.TESTFN
old = sys.getrefcount(path)
@@ -83,6 +97,25 @@ class FileTests(unittest.TestCase):
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)
+
class TemporaryFileTests(unittest.TestCase):
def setUp(self):
@@ -165,7 +198,6 @@ class TemporaryFileTests(unittest.TestCase):
self.assertTrue(s == "foobar")
def test_tmpnam(self):
- import sys
if not hasattr(os, "tmpnam"):
return
warnings.filterwarnings("ignore", "tmpnam", RuntimeWarning,
@@ -215,19 +247,17 @@ class StatAttributeTests(unittest.TestCase):
os.unlink(self.fname)
os.rmdir(support.TESTFN)
- def test_stat_attributes(self):
+ def check_stat_attributes(self, fname):
if not hasattr(os, "stat"):
return
import stat
- result = os.stat(self.fname)
+ result = os.stat(fname)
# Make sure direct access works
self.assertEqual(result[stat.ST_SIZE], 3)
self.assertEqual(result.st_size, 3)
- import sys
-
# Make sure all the attributes are there
members = dir(result)
for name in dir(stat):
@@ -238,8 +268,8 @@ class StatAttributeTests(unittest.TestCase):
else:
def trunc(x): return x
self.assertEqual(trunc(getattr(result, attr)),
- result[getattr(stat, name)])
- self.assertTrue(attr in members)
+ result[getattr(stat, name)])
+ self.assertIn(attr, members)
try:
result[200]
@@ -279,6 +309,15 @@ class StatAttributeTests(unittest.TestCase):
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_statvfs_attributes(self):
if not hasattr(os, "statvfs"):
@@ -374,12 +413,17 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
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"}
@@ -422,9 +466,73 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
def test___repr__(self):
"""Check that the repr() of os.environ looks like environ({...})."""
env = os.environ
- self.assertTrue(isinstance(env.data, dict))
- self.assertEqual(repr(env), 'environ({!r})'.format(env.data))
+ self.assertEqual(repr(env), 'environ({{{}}})'.format(', '.join(
+ '{!r}: {!r}'.format(key, value)
+ for key, value in env.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)
class WalkTests(unittest.TestCase):
"""Tests for os.walk()."""
@@ -464,7 +572,7 @@ class WalkTests(unittest.TestCase):
f = open(path, "w")
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
f.close()
- if hasattr(os, "symlink"):
+ if support.can_symlink():
os.symlink(os.path.abspath(t2_path), link_path)
sub2_tree = (sub2_path, ["link"], ["tmp3"])
else:
@@ -508,7 +616,7 @@ class WalkTests(unittest.TestCase):
self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"]))
self.assertEqual(all[2 - 2 * flipped], sub2_tree)
- if hasattr(os, "symlink"):
+ if support.can_symlink():
# Walk, following symlinks.
for root, dirs, files in os.walk(walk_path, followlinks=True):
if root == link_path:
@@ -553,6 +661,28 @@ class MakedirTests(unittest.TestCase):
'dir5', 'dir6')
os.makedirs(path)
+ def test_exist_ok_existing_directory(self):
+ path = os.path.join(support.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)
+ self.assertRaises(OSError, os.makedirs, path, 0o776, exist_ok=True)
+ os.makedirs(path, mode=mode, exist_ok=True)
+ os.umask(old_mask)
+
+ def test_exist_ok_existing_regular_file(self):
+ base = support.TESTFN
+ path = os.path.join(support.TESTFN, 'dir1')
+ f = open(path, 'w')
+ f.write('abc')
+ f.close()
+ self.assertRaises(OSError, os.makedirs, path)
+ self.assertRaises(OSError, os.makedirs, path, exist_ok=False)
+ self.assertRaises(OSError, os.makedirs, path, exist_ok=True)
+ os.remove(path)
+
def tearDown(self):
path = os.path.join(support.TESTFN, 'dir1', 'dir2', 'dir3',
'dir4', 'dir5', 'dir6')
@@ -583,6 +713,39 @@ class URandomTests(unittest.TestCase):
except NotImplementedError:
pass
+@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
+
class ExecTests(unittest.TestCase):
@unittest.skipIf(USING_LINUXTHREADS,
"avoid triggering a linuxthreads bug: see issue #4970")
@@ -593,13 +756,60 @@ class ExecTests(unittest.TestCase):
def test_execvpe_with_bad_arglist(self):
self.assertRaises(ValueError, os.execvpe, 'notepad', [], None)
-class ArgTests(unittest.TestCase):
- def test_bytearray(self):
- # Issue #7561: posix module didn't release bytearray exports properly.
- b = bytearray(os.sep.encode('ascii'))
- self.assertRaises(OSError, os.mkdir, b)
- # Check object is still resizable.
- b[:] = b''
+ @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)
+
class Win32ErrorTests(unittest.TestCase):
def test_rename(self):
@@ -703,6 +913,42 @@ class TestInvalidFD(unittest.TestCase):
if hasattr(os, "write"):
self.check(os.write, b" ")
+
+class LinkTests(unittest.TestCase):
+ def setUp(self):
+ self.file1 = support.TESTFN
+ self.file2 = os.path.join(support.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):
+ with open(file1, "w") as f1:
+ f1.write("test")
+
+ os.link(file1, file2)
+ with open(file1, "r") as f1, open(file2, "r") 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)
+
if sys.platform != 'win32':
class Win32ErrorTests(unittest.TestCase):
pass
@@ -742,7 +988,6 @@ if sys.platform != 'win32':
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).
- import subprocess
subprocess.check_call([
sys.executable, '-c',
'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
@@ -757,34 +1002,51 @@ if sys.platform != 'win32':
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).
- import subprocess
subprocess.check_call([
sys.executable, '-c',
'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
- @unittest.skipIf(sys.platform == 'darwin', "tests don't apply to OS X")
class Pep383Tests(unittest.TestCase):
- filenames = [b'foo\xf6bar', 'foo\xf6bar'.encode("utf-8")]
-
def setUp(self):
- self.fsencoding = sys.getfilesystemencoding()
- sys.setfilesystemencoding("utf-8")
- self.dir = support.TESTFN
- self.bdir = self.dir.encode("utf-8", "surrogateescape")
+ if support.TESTFN_UNENCODABLE:
+ self.dir = support.TESTFN_UNENCODABLE
+ else:
+ self.dir = support.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(support.TESTFN_UNICODE)
+ if support.TESTFN_UNENCODABLE:
+ add_filename(support.TESTFN_UNENCODABLE)
+ if not bytesfn:
+ self.skipTest("couldn't create any non-ascii filename")
+
+ self.unicodefn = set()
os.mkdir(self.dir)
- self.unicodefn = []
- for fn in self.filenames:
- f = open(os.path.join(self.bdir, fn), "w")
- f.close()
- self.unicodefn.append(fn.decode("utf-8", "surrogateescape"))
+ try:
+ for fn in bytesfn:
+ f = open(os.path.join(self.bdir, fn), "w")
+ f.close()
+ 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)
- sys.setfilesystemencoding(self.fsencoding)
def test_listdir(self):
- expected = set(self.unicodefn)
- found = set(os.listdir(support.TESTFN))
+ expected = self.unicodefn
+ found = set(os.listdir(self.dir))
self.assertEqual(found, expected)
def test_open(self):
@@ -801,9 +1063,513 @@ else:
class Pep383Tests(unittest.TestCase):
pass
+@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)
+
+ 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.
+ proc = subprocess.Popen([sys.executable,
+ os.path.join(os.path.dirname(__file__),
+ "win_console_handler.py"), tagname],
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
+ # Let the interpreter startup before we send signals. See #3137.
+ count, max = 0, 100
+ while count < max and proc.poll() is None:
+ if m[0] == 1:
+ break
+ time.sleep(0.1)
+ count += 1
+ else:
+ # Forcefully kill the process if we weren't able to signal it.
+ os.kill(proc.pid, signal.SIGINT)
+ self.fail("Subprocess didn't finish initialization")
+ os.kill(proc.pid, event)
+ # proc.send_signal(event) could also be done here.
+ # Allow time for the signal to be passed and the process to exit.
+ time.sleep(0.5)
+ if not proc.poll():
+ # Forcefully kill the process if we weren't able to signal it.
+ os.kill(proc.pid, signal.SIGINT)
+ self.fail("subprocess did not stop on {}".format(name))
+
+ @unittest.skip("subprocesses aren't inheriting CTRL+C property")
+ 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")
+
+ 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")
+@support.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)
+
+ @unittest.skip("currently fails; consider for improvement")
+ def test_isdir_on_directory_link_to_missing_target(self):
+ self._create_missing_dir_link()
+ # consider having isdir return true for directory links
+ self.assertTrue(os.path.isdir(self.missing_link))
+
+ @unittest.skip("currently fails; consider for improvement")
+ def test_rmdir_on_directory_link_to_missing_target(self):
+ self._create_missing_dir_link()
+ # consider allowing rmdir to remove directory links
+ 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))
+
+
+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 PidTests(unittest.TestCase):
+ @unittest.skipUnless(hasattr(os, 'getppid'), "test needs os.getppid")
+ def test_getppid(self):
+ p = subprocess.Popen([sys.executable, '-c',
+ 'import os; print(os.getppid())'],
+ stdout=subprocess.PIPE)
+ stdout, _ = p.communicate()
+ # We are the parent of our subprocess
+ self.assertEqual(int(stdout), os.getpid())
+
+
+# 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())
+ os.setpriority(os.PRIO_PROCESS, os.getpid(), base + 1)
+ try:
+ new_prio = os.getpriority(os.PRIO_PROCESS, os.getpid())
+ if base >= 19 and new_prio <= 19:
+ raise unittest.SkipTest(
+ "unable to reliably test setpriority at current nice level of %s" % base)
+ else:
+ self.assertEqual(new_prio, base + 1)
+ finally:
+ try:
+ os.setpriority(os.PRIO_PROCESS, os.getpid(), base)
+ except OSError as err:
+ if err.errno != errno.EACCES:
+ raise
+
+
+class SendfileTestServer(asyncore.dispatcher, threading.Thread):
+
+ class Handler(asynchat.async_chat):
+
+ def __init__(self, conn):
+ asynchat.async_chat.__init__(self, conn)
+ self.in_buffer = []
+ self.closed = False
+ self.push(b"220 ready\r\n")
+
+ def handle_read(self):
+ data = self.recv(4096)
+ self.in_buffer.append(data)
+
+ def get_data(self):
+ return b''.join(self.in_buffer)
+
+ def handle_close(self):
+ self.close()
+ self.closed = True
+
+ def handle_error(self):
+ raise
+
+ def __init__(self, address):
+ threading.Thread.__init__(self)
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.bind(address)
+ self.listen(5)
+ self.host, self.port = self.socket.getsockname()[:2]
+ self.handler_instance = None
+ self._active = False
+ self._active_lock = threading.Lock()
+
+ # --- public API
+
+ @property
+ def running(self):
+ return self._active
+
+ def start(self):
+ assert not self.running
+ self.__flag = threading.Event()
+ threading.Thread.start(self)
+ self.__flag.wait()
+
+ def stop(self):
+ assert self.running
+ self._active = False
+ self.join()
+
+ def wait(self):
+ # wait for handler connection to be closed, then stop the server
+ while not getattr(self.handler_instance, "closed", False):
+ time.sleep(0.001)
+ self.stop()
+
+ # --- internals
+
+ def run(self):
+ self._active = True
+ self.__flag.set()
+ while self._active and asyncore.socket_map:
+ self._active_lock.acquire()
+ asyncore.loop(timeout=0.001, count=1)
+ self._active_lock.release()
+ asyncore.close_all()
+
+ def handle_accept(self):
+ conn, addr = self.accept()
+ self.handler_instance = self.Handler(conn)
+
+ def handle_connect(self):
+ self.close()
+ handle_read = handle_connect
+
+ def writable(self):
+ return 0
+
+ def handle_error(self):
+ raise
+
+
+@unittest.skipUnless(threading is not None, "test needs threading module")
+@unittest.skipUnless(hasattr(os, 'sendfile'), "test needs os.sendfile()")
+class TestSendfile(unittest.TestCase):
+
+ DATA = b"12345abcde" * 16 * 1024 # 160 KB
+ SUPPORT_HEADERS_TRAILERS = not sys.platform.startswith("linux") and \
+ not sys.platform.startswith("solaris") and \
+ not sys.platform.startswith("sunos")
+
+ @classmethod
+ def setUpClass(cls):
+ with open(support.TESTFN, "wb") as f:
+ f.write(cls.DATA)
+
+ @classmethod
+ def tearDownClass(cls):
+ support.unlink(support.TESTFN)
+
+ def setUp(self):
+ self.server = SendfileTestServer((support.HOST, 0))
+ self.server.start()
+ self.client = socket.socket()
+ self.client.connect((self.server.host, self.server.port))
+ self.client.settimeout(1)
+ # synchronize by waiting for "220 ready" response
+ self.client.recv(1024)
+ self.sockno = self.client.fileno()
+ self.file = open(support.TESTFN, 'rb')
+ self.fileno = self.file.fileno()
+
+ def tearDown(self):
+ self.file.close()
+ self.client.close()
+ if self.server.running:
+ self.server.stop()
+
+ def sendfile_wrapper(self, sock, file, offset, nbytes, headers=[], trailers=[]):
+ """A higher level wrapper representing how an application is
+ supposed to use sendfile().
+ """
+ while 1:
+ try:
+ if self.SUPPORT_HEADERS_TRAILERS:
+ return os.sendfile(sock, file, offset, nbytes, headers,
+ trailers)
+ else:
+ return os.sendfile(sock, file, offset, nbytes)
+ 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
+
+ def test_send_whole_file(self):
+ # normal send
+ total_sent = 0
+ offset = 0
+ nbytes = 4096
+ while total_sent < len(self.DATA):
+ sent = 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()
+ self.server.wait()
+ data = self.server.handler_instance.get_data()
+ self.assertEqual(len(data), len(self.DATA))
+ self.assertEqual(data, self.DATA)
+
+ 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 = 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()
+ self.server.wait()
+ data = self.server.handler_instance.get_data()
+ expected = self.DATA[len(self.DATA) // 2:]
+ self.assertEqual(total_sent, len(expected))
+ self.assertEqual(len(data), len(expected))
+ self.assertEqual(data, expected)
+
+ def test_offset_overflow(self):
+ # specify an offset > file size
+ offset = len(self.DATA) + 4096
+ try:
+ sent = os.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()
+ self.server.wait()
+ data = self.server.handler_instance.get_data()
+ self.assertEqual(data, b'')
+
+ def test_invalid_offset(self):
+ with self.assertRaises(OSError) as cm:
+ os.sendfile(self.sockno, self.fileno, -1, 4096)
+ self.assertEqual(cm.exception.errno, errno.EINVAL)
+
+ # --- headers / trailers tests
+
+ if SUPPORT_HEADERS_TRAILERS:
+
+ def test_headers(self):
+ total_sent = 0
+ sent = os.sendfile(self.sockno, self.fileno, 0, 4096,
+ headers=[b"x" * 512])
+ total_sent += sent
+ offset = 4096
+ nbytes = 4096
+ while 1:
+ sent = self.sendfile_wrapper(self.sockno, self.fileno,
+ offset, nbytes)
+ if sent == 0:
+ break
+ total_sent += sent
+ offset += sent
+
+ expected_data = b"x" * 512 + self.DATA
+ self.assertEqual(total_sent, len(expected_data))
+ self.client.close()
+ self.server.wait()
+ data = self.server.handler_instance.get_data()
+ self.assertEqual(hash(data), hash(expected_data))
+
+ def test_trailers(self):
+ TESTFN2 = support.TESTFN + "2"
+ with open(TESTFN2, 'wb') as f:
+ f.write(b"abcde")
+ with open(TESTFN2, 'rb')as f:
+ self.addCleanup(os.remove, TESTFN2)
+ os.sendfile(self.sockno, f.fileno(), 0, 4096,
+ trailers=[b"12345"])
+ self.client.close()
+ self.server.wait()
+ data = self.server.handler_instance.get_data()
+ self.assertEqual(data, b"abcde12345")
+
+ if hasattr(os, "SF_NODISKIO"):
+ def test_flags(self):
+ try:
+ os.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 test_main():
support.run_unittest(
- ArgTests,
FileTests,
StatAttributeTests,
EnvironTests,
@@ -815,7 +1581,15 @@ def test_main():
Win32ErrorTests,
TestInvalidFD,
PosixUidGidTests,
- Pep383Tests
+ Pep383Tests,
+ Win32KillTests,
+ Win32SymlinkTests,
+ FSEncodingTests,
+ PidTests,
+ LoginTests,
+ LinkTests,
+ TestSendfile,
+ ProgramPriorityTests,
)
if __name__ == "__main__":