##// END OF EJS Templates
Added signature for changeset 411dc27fd9fd
Added signature for changeset 411dc27fd9fd

File last commit:

r48294:23f5ed6d default
r48358:29ea3b4c stable
Show More
test-stdio.py
365 lines | 11.1 KiB | text/x-python | PythonLexer
Gregory Szorc
global: use python3 in shebangs...
r46434 #!/usr/bin/env python3
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 """
Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
"""
from __future__ import absolute_import
import contextlib
Manuel Jacob
tests: proof test-stdio.py against buffer fill-up...
r45628 import errno
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 import os
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 import signal
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 import subprocess
import sys
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 import tempfile
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 import unittest
Manuel Jacob
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
r45708 from mercurial import pycompat, util
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
Manuel Jacob
tests: make pipes / PTYs non-inheritable in test-stdio.py...
r45707 if pycompat.ispy3:
def set_noninheritable(fd):
# On Python 3, file descriptors are non-inheritable by default.
pass
else:
if pycompat.iswindows:
# unused
set_noninheritable = None
else:
import fcntl
def set_noninheritable(fd):
old = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 TEST_BUFFERING_CHILD_SCRIPT = r'''
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 import os
from mercurial import dispatch
from mercurial.utils import procutil
dispatch.initstdio()
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 procutil.{stream}.write(b'aaa')
os.write(procutil.{stream}.fileno(), b'[written aaa]')
procutil.{stream}.write(b'bbb\n')
os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 '''
UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
Manuel Jacob
procutil: avoid use of deprecated tempfile.mktemp()...
r45664 import os
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 import signal
import sys
from mercurial import dispatch
from mercurial.utils import procutil
signal.signal(signal.SIGINT, lambda *x: None)
dispatch.initstdio()
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 write_result = procutil.{stream}.write(b'x' * 1048576)
Manuel Jacob
procutil: avoid use of deprecated tempfile.mktemp()...
r45664 with os.fdopen(
os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)),
'w',
) as write_result_f:
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 write_result_f.write(str(write_result))
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 '''
Manuel Jacob
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
r45708 TEST_BROKEN_PIPE_CHILD_SCRIPT = r'''
import os
import pickle
from mercurial import dispatch
from mercurial.utils import procutil
dispatch.initstdio()
procutil.stdin.read(1) # wait until parent process closed pipe
try:
procutil.{stream}.write(b'test')
procutil.{stream}.flush()
except EnvironmentError as e:
with os.fdopen(
os.open(
{err_fn!r},
os.O_WRONLY
| getattr(os, 'O_BINARY', 0)
| getattr(os, 'O_TEMPORARY', 0),
),
'wb',
) as err_f:
pickle.dump(e, err_f)
# Exit early to suppress further broken pipe errors at interpreter shutdown.
os._exit(0)
'''
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 @contextlib.contextmanager
def _closing(fds):
try:
yield
finally:
for fd in fds:
try:
os.close(fd)
except EnvironmentError:
pass
Manuel Jacob
tests: make pipes / PTYs non-inheritable in test-stdio.py...
r45707 # In the following, we set the FDs non-inheritable mainly to make it possible
# for tests to close the receiving end of the pipe / PTYs.
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 @contextlib.contextmanager
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def _devnull():
devnull = os.open(os.devnull, os.O_WRONLY)
Manuel Jacob
tests: make pipes / PTYs non-inheritable in test-stdio.py...
r45707 # We don't have a receiving end, so it's not worth the effort on Python 2
# on Windows to make the FD non-inheritable.
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 with _closing([devnull]):
yield (None, devnull)
@contextlib.contextmanager
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 def _pipes():
rwpair = os.pipe()
Manuel Jacob
tests: make pipes / PTYs non-inheritable in test-stdio.py...
r45707 # Pipes are already non-inheritable on Windows.
if not pycompat.iswindows:
set_noninheritable(rwpair[0])
set_noninheritable(rwpair[1])
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 with _closing(rwpair):
yield rwpair
@contextlib.contextmanager
def _ptys():
if pycompat.iswindows:
raise unittest.SkipTest("PTYs are not supported on Windows")
import pty
import tty
rwpair = pty.openpty()
Manuel Jacob
tests: make pipes / PTYs non-inheritable in test-stdio.py...
r45707 set_noninheritable(rwpair[0])
set_noninheritable(rwpair[1])
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 with _closing(rwpair):
tty.setraw(rwpair[0])
yield rwpair
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def _readall(fd, buffer_size, initial_buf=None):
buf = initial_buf or []
Manuel Jacob
tests: proof test-stdio.py against buffer fill-up...
r45628 while True:
try:
s = os.read(fd, buffer_size)
except OSError as e:
if e.errno == errno.EIO:
# If the child-facing PTY got closed, reading from the
# parent-facing PTY raises EIO.
break
raise
if not s:
break
buf.append(s)
return b''.join(buf)
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 class TestStdio(unittest.TestCase):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 def _test(
self,
child_script,
stream,
rwpair_generator,
check_output,
python_args=[],
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 post_child_check=None,
Manuel Jacob
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
r45708 stdin_generator=None,
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 ):
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 assert stream in ('stdout', 'stderr')
Manuel Jacob
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
r45708 if stdin_generator is None:
stdin_generator = open(os.devnull, 'rb')
with rwpair_generator() as (
stream_receiver,
child_stream,
), stdin_generator as child_stdin:
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 proc = subprocess.Popen(
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 [sys.executable] + python_args + ['-c', child_script],
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 stdin=child_stdin,
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 stdout=child_stream if stream == 'stdout' else None,
stderr=child_stream if stream == 'stderr' else None,
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 )
Manuel Jacob
tests: proof test-stdio.py against buffer fill-up...
r45628 try:
os.close(child_stream)
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 if stream_receiver is not None:
check_output(stream_receiver, proc)
Manuel Jacob
tests: terminate subprocess in test-stdio.py in case of exception...
r45629 except: # re-raises
proc.terminate()
raise
Manuel Jacob
tests: proof test-stdio.py against buffer fill-up...
r45628 finally:
retcode = proc.wait()
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 self.assertEqual(retcode, 0)
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 if post_child_check is not None:
post_child_check()
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 def _test_buffering(
self, stream, rwpair_generator, expected_output, python_args=[]
):
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def check_output(stream_receiver, proc):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
self._test(
TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
stream,
rwpair_generator,
check_output,
python_args,
)
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_buffering_stdout_devnull(self):
self._test_buffering('stdout', _devnull, None)
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 def test_buffering_stdout_pipes(self):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 def test_buffering_stdout_ptys(self):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_buffering_stdout_devnull_unbuffered(self):
self._test_buffering('stdout', _devnull, None, python_args=['-u'])
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 def test_buffering_stdout_pipes_unbuffered(self):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 def test_buffering_stdout_ptys_unbuffered(self):
Manuel Jacob
tests: make subprocess handling reusable for different tests in test-stdio.py
r45638 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
if not pycompat.ispy3 and not pycompat.iswindows:
# On Python 2 on non-Windows, we manually open stdout in line-buffered
# mode if connected to a TTY. We should check if Python was configured
# to use unbuffered stdout, but it's hard to do that.
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
test_buffering_stdout_ptys_unbuffered
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 )
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
if not pycompat.ispy3 and pycompat.isdarwin:
# Python 2 doesn't always retry on EINTR, but the libc might retry.
# So far, it was observed only on macOS that EINTR is raised at the
# Python level. As Python 2 support will be dropped soon-ish, we
# won't attempt to fix it.
raise unittest.SkipTest("raises EINTR on macOS")
def check_output(stream_receiver, proc):
if not pycompat.iswindows:
# On Unix, we can provoke a partial write() by interrupting it
# by a signal handler as soon as a bit of data was written.
# We test that write() is called until all data is written.
buf = [os.read(stream_receiver, 1)]
proc.send_signal(signal.SIGINT)
else:
# On Windows, there doesn't seem to be a way to cause partial
# writes.
buf = []
self.assertEqual(
_readall(stream_receiver, 131072, buf), b'x' * 1048576
)
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 def post_child_check():
Manuel Jacob
procutil: avoid use of deprecated tempfile.mktemp()...
r45664 write_result_str = write_result_f.read()
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 if pycompat.ispy3:
# On Python 3, we test that the correct number of bytes is
# claimed to have been written.
expected_write_result_str = '1048576'
else:
# On Python 2, we only check that the large write does not
# crash.
expected_write_result_str = 'None'
self.assertEqual(write_result_str, expected_write_result_str)
Manuel Jacob
procutil: avoid use of deprecated tempfile.mktemp()...
r45664 with tempfile.NamedTemporaryFile('r') as write_result_f:
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 self._test(
TEST_LARGE_WRITE_CHILD_SCRIPT.format(
Manuel Jacob
procutil: avoid use of deprecated tempfile.mktemp()...
r45664 stream=stream, write_result_fn=write_result_f.name
Manuel Jacob
tests: check that procutil.std{out,err}.write() returns correct result...
r45657 ),
stream,
rwpair_generator,
check_output,
python_args,
post_child_check=post_child_check,
)
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_large_write_stdout_devnull(self):
self._test_large_write('stdout', _devnull)
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def test_large_write_stdout_pipes(self):
self._test_large_write('stdout', _pipes)
def test_large_write_stdout_ptys(self):
self._test_large_write('stdout', _ptys)
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_large_write_stdout_devnull_unbuffered(self):
self._test_large_write('stdout', _devnull, python_args=['-u'])
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def test_large_write_stdout_pipes_unbuffered(self):
self._test_large_write('stdout', _pipes, python_args=['-u'])
def test_large_write_stdout_ptys_unbuffered(self):
self._test_large_write('stdout', _ptys, python_args=['-u'])
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_large_write_stderr_devnull(self):
self._test_large_write('stderr', _devnull)
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def test_large_write_stderr_pipes(self):
self._test_large_write('stderr', _pipes)
def test_large_write_stderr_ptys(self):
self._test_large_write('stderr', _ptys)
Manuel Jacob
tests: add tests for when stdout or stderr is connected to `os.devnull`...
r45656 def test_large_write_stderr_devnull_unbuffered(self):
self._test_large_write('stderr', _devnull, python_args=['-u'])
Manuel Jacob
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
r45655 def test_large_write_stderr_pipes_unbuffered(self):
self._test_large_write('stderr', _pipes, python_args=['-u'])
def test_large_write_stderr_ptys_unbuffered(self):
self._test_large_write('stderr', _ptys, python_args=['-u'])
Manuel Jacob
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
r45708 def _test_broken_pipe(self, stream):
assert stream in ('stdout', 'stderr')
def check_output(stream_receiver, proc):
os.close(stream_receiver)
proc.stdin.write(b'x')
proc.stdin.close()
def post_child_check():
err = util.pickle.load(err_f)
self.assertEqual(err.errno, errno.EPIPE)
self.assertEqual(err.strerror, "Broken pipe")
with tempfile.NamedTemporaryFile('rb') as err_f:
self._test(
TEST_BROKEN_PIPE_CHILD_SCRIPT.format(
stream=stream, err_fn=err_f.name
),
stream,
_pipes,
check_output,
post_child_check=post_child_check,
stdin_generator=util.nullcontextmanager(subprocess.PIPE),
)
def test_broken_pipe_stdout(self):
self._test_broken_pipe('stdout')
def test_broken_pipe_stderr(self):
self._test_broken_pipe('stderr')
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583
if __name__ == '__main__':
import silenttestrunner
silenttestrunner.main(__name__)