##// END OF EJS Templates
procutil: back out 8403cc54bc83 (make ....procutil.stderr unbuffered)...
procutil: back out 8403cc54bc83 (make ....procutil.stderr unbuffered) Changeset 8403cc54bc83 introduced code that opens a second file object referring to the stderr file descriptor. This broke tests on Windows. The reason is that on Windows, sys.stderr is buffered and procutil.stderr closed the file descriptor when it got garbage collected before sys.stderr had the chance to flush buffered data. `procutil.stdout` had the same problem for a long time, but we didn’t realize, as in CI test runs, stdout is not a TTY and in this case no second file object is opened.

File last commit:

r45637:fa270dcb default
r45637:fa270dcb default
Show More
test-stdio.py
133 lines | 3.6 KiB | text/x-python | PythonLexer
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 #!/usr/bin/env python
"""
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
import subprocess
import sys
import unittest
from mercurial import pycompat
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 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'
@contextlib.contextmanager
def _closing(fds):
try:
yield
finally:
for fd in fds:
try:
os.close(fd)
except EnvironmentError:
pass
@contextlib.contextmanager
def _pipes():
rwpair = os.pipe()
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()
with _closing(rwpair):
tty.setraw(rwpair[0])
yield rwpair
Manuel Jacob
tests: proof test-stdio.py against buffer fill-up...
r45628 def _readall(fd, buffer_size):
buf = []
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):
def _test(self, stream, rwpair_generator, expected_output, python_args=[]):
assert stream in ('stdout', 'stderr')
with rwpair_generator() as (stream_receiver, child_stream), open(
Manuel Jacob
tests: add tests for buffering behavior of mercurial.utils.procutil.stdout
r45583 os.devnull, 'rb'
) as child_stdin:
proc = subprocess.Popen(
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 [sys.executable]
+ python_args
Manuel Jacob
tests: make names in test-stdio.py more distinctive...
r45630 + ['-c', BUFFERING_CHILD_SCRIPT.format(stream=stream)],
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)
self.assertEqual(
_readall(stream_receiver, 1024), expected_output
)
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: make names in test-stdio.py more distinctive...
r45630 def test_buffering_stdout_pipes(self):
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 self._test('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: generalize common test case code in test-stdio.py
r45589 self._test('stdout', _ptys, LINE_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_pipes_unbuffered(self):
Manuel Jacob
tests: generalize common test case code in test-stdio.py
r45589 self._test('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: generalize common test case code in test-stdio.py
r45589 self._test('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 )
if __name__ == '__main__':
import silenttestrunner
silenttestrunner.main(__name__)