259 lines
7.3 KiB
Python
259 lines
7.3 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Tests for L{twisted.internet.fdesc}.
|
|
"""
|
|
|
|
import errno
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
import fcntl
|
|
except ImportError:
|
|
skip = "not supported on this platform"
|
|
else:
|
|
from twisted.internet import fdesc
|
|
|
|
from twisted.python.util import untilConcludes
|
|
from twisted.trial import unittest
|
|
|
|
|
|
class NonBlockingTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{fdesc.setNonBlocking} and L{fdesc.setBlocking}.
|
|
"""
|
|
|
|
def test_setNonBlocking(self):
|
|
"""
|
|
L{fdesc.setNonBlocking} sets a file description to non-blocking.
|
|
"""
|
|
r, w = os.pipe()
|
|
self.addCleanup(os.close, r)
|
|
self.addCleanup(os.close, w)
|
|
self.assertFalse(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
|
|
fdesc.setNonBlocking(r)
|
|
self.assertTrue(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
|
|
|
|
def test_setBlocking(self):
|
|
"""
|
|
L{fdesc.setBlocking} sets a file description to blocking.
|
|
"""
|
|
r, w = os.pipe()
|
|
self.addCleanup(os.close, r)
|
|
self.addCleanup(os.close, w)
|
|
fdesc.setNonBlocking(r)
|
|
fdesc.setBlocking(r)
|
|
self.assertFalse(fcntl.fcntl(r, fcntl.F_GETFL) & os.O_NONBLOCK)
|
|
|
|
|
|
class ReadWriteTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{fdesc.readFromFD}, L{fdesc.writeToFD}.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Create a non-blocking pipe that can be used in tests.
|
|
"""
|
|
self.r, self.w = os.pipe()
|
|
fdesc.setNonBlocking(self.r)
|
|
fdesc.setNonBlocking(self.w)
|
|
|
|
def tearDown(self):
|
|
"""
|
|
Close pipes.
|
|
"""
|
|
try:
|
|
os.close(self.w)
|
|
except OSError:
|
|
pass
|
|
try:
|
|
os.close(self.r)
|
|
except OSError:
|
|
pass
|
|
|
|
def write(self, d):
|
|
"""
|
|
Write data to the pipe.
|
|
"""
|
|
return fdesc.writeToFD(self.w, d)
|
|
|
|
def read(self):
|
|
"""
|
|
Read data from the pipe.
|
|
"""
|
|
l = []
|
|
res = fdesc.readFromFD(self.r, l.append)
|
|
if res is None:
|
|
if l:
|
|
return l[0]
|
|
else:
|
|
return b""
|
|
else:
|
|
return res
|
|
|
|
def test_writeAndRead(self):
|
|
"""
|
|
Test that the number of bytes L{fdesc.writeToFD} reports as written
|
|
with its return value are seen by L{fdesc.readFromFD}.
|
|
"""
|
|
n = self.write(b"hello")
|
|
self.assertTrue(n > 0)
|
|
s = self.read()
|
|
self.assertEqual(len(s), n)
|
|
self.assertEqual(b"hello"[:n], s)
|
|
|
|
def test_writeAndReadLarge(self):
|
|
"""
|
|
Similar to L{test_writeAndRead}, but use a much larger string to verify
|
|
the behavior for that case.
|
|
"""
|
|
orig = b"0123456879" * 10000
|
|
written = self.write(orig)
|
|
self.assertTrue(written > 0)
|
|
result = []
|
|
resultlength = 0
|
|
i = 0
|
|
while resultlength < written or i < 50:
|
|
result.append(self.read())
|
|
resultlength += len(result[-1])
|
|
# Increment a counter to be sure we'll exit at some point
|
|
i += 1
|
|
result = b"".join(result)
|
|
self.assertEqual(len(result), written)
|
|
self.assertEqual(orig[:written], result)
|
|
|
|
def test_readFromEmpty(self):
|
|
"""
|
|
Verify that reading from a file descriptor with no data does not raise
|
|
an exception and does not result in the callback function being called.
|
|
"""
|
|
l = []
|
|
result = fdesc.readFromFD(self.r, l.append)
|
|
self.assertEqual(l, [])
|
|
self.assertIsNone(result)
|
|
|
|
def test_readFromCleanClose(self):
|
|
"""
|
|
Test that using L{fdesc.readFromFD} on a cleanly closed file descriptor
|
|
returns a connection done indicator.
|
|
"""
|
|
os.close(self.w)
|
|
self.assertEqual(self.read(), fdesc.CONNECTION_DONE)
|
|
|
|
def test_writeToClosed(self):
|
|
"""
|
|
Verify that writing with L{fdesc.writeToFD} when the read end is closed
|
|
results in a connection lost indicator.
|
|
"""
|
|
os.close(self.r)
|
|
self.assertEqual(self.write(b"s"), fdesc.CONNECTION_LOST)
|
|
|
|
def test_readFromInvalid(self):
|
|
"""
|
|
Verify that reading with L{fdesc.readFromFD} when the read end is
|
|
closed results in a connection lost indicator.
|
|
"""
|
|
os.close(self.r)
|
|
self.assertEqual(self.read(), fdesc.CONNECTION_LOST)
|
|
|
|
def test_writeToInvalid(self):
|
|
"""
|
|
Verify that writing with L{fdesc.writeToFD} when the write end is
|
|
closed results in a connection lost indicator.
|
|
"""
|
|
os.close(self.w)
|
|
self.assertEqual(self.write(b"s"), fdesc.CONNECTION_LOST)
|
|
|
|
def test_writeErrors(self):
|
|
"""
|
|
Test error path for L{fdesc.writeTod}.
|
|
"""
|
|
oldOsWrite = os.write
|
|
|
|
def eagainWrite(fd, data):
|
|
err = OSError()
|
|
err.errno = errno.EAGAIN
|
|
raise err
|
|
|
|
os.write = eagainWrite
|
|
try:
|
|
self.assertEqual(self.write(b"s"), 0)
|
|
finally:
|
|
os.write = oldOsWrite
|
|
|
|
def eintrWrite(fd, data):
|
|
err = OSError()
|
|
err.errno = errno.EINTR
|
|
raise err
|
|
|
|
os.write = eintrWrite
|
|
try:
|
|
self.assertEqual(self.write(b"s"), 0)
|
|
finally:
|
|
os.write = oldOsWrite
|
|
|
|
|
|
class CloseOnExecTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{fdesc._setCloseOnExec} and L{fdesc._unsetCloseOnExec}.
|
|
"""
|
|
|
|
program = """
|
|
import os, errno
|
|
try:
|
|
os.write(%d, b'lul')
|
|
except OSError as e:
|
|
if e.errno == errno.EBADF:
|
|
os._exit(0)
|
|
os._exit(5)
|
|
except BaseException:
|
|
os._exit(10)
|
|
else:
|
|
os._exit(20)
|
|
"""
|
|
|
|
def _execWithFileDescriptor(self, fObj):
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
try:
|
|
os.execv(
|
|
sys.executable,
|
|
[sys.executable, "-c", self.program % (fObj.fileno(),)],
|
|
)
|
|
except BaseException:
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
os._exit(30)
|
|
else:
|
|
# On Linux wait(2) doesn't seem ever able to fail with EINTR but
|
|
# POSIX seems to allow it and on macOS it happens quite a lot.
|
|
return untilConcludes(os.waitpid, pid, 0)[1]
|
|
|
|
def test_setCloseOnExec(self):
|
|
"""
|
|
A file descriptor passed to L{fdesc._setCloseOnExec} is not inherited
|
|
by a new process image created with one of the exec family of
|
|
functions.
|
|
"""
|
|
with open(self.mktemp(), "wb") as fObj:
|
|
fdesc._setCloseOnExec(fObj.fileno())
|
|
status = self._execWithFileDescriptor(fObj)
|
|
self.assertTrue(os.WIFEXITED(status))
|
|
self.assertEqual(os.WEXITSTATUS(status), 0)
|
|
|
|
def test_unsetCloseOnExec(self):
|
|
"""
|
|
A file descriptor passed to L{fdesc._unsetCloseOnExec} is inherited by
|
|
a new process image created with one of the exec family of functions.
|
|
"""
|
|
with open(self.mktemp(), "wb") as fObj:
|
|
fdesc._setCloseOnExec(fObj.fileno())
|
|
fdesc._unsetCloseOnExec(fObj.fileno())
|
|
status = self._execWithFileDescriptor(fObj)
|
|
self.assertTrue(os.WIFEXITED(status))
|
|
self.assertEqual(os.WEXITSTATUS(status), 20)
|