comparison tests/test-stdio.py @ 45148:a37f290a7124

windows: always work around EINVAL in case of broken pipe for stdout / stderr In 29a905fe23ae, I missed the fact that the `winstdout` class works around two unrelated bugs (size limit when writing to consoles and EINVAL in case of broken pipe) and that the latter bug happens even when no console is involved. When writing a test for this, I realized that the same problem applies to stderr, so I applied the workaround for EINVAL to both stdout and stderr. The size limit is worked around in the same case as before (consoles on Windows on Python 2). For that, I changed the `winstdout` class.
author Manuel Jacob <me@manueljacob.de>
date Fri, 17 Jul 2020 03:28:52 +0200
parents c2c862b9b544
children c102b704edb5
comparison
equal deleted inserted replaced
45147:c2c862b9b544 45148:a37f290a7124
11 import subprocess 11 import subprocess
12 import sys 12 import sys
13 import tempfile 13 import tempfile
14 import unittest 14 import unittest
15 15
16 from mercurial import pycompat 16 from mercurial import pycompat, util
17 17
18 18
19 if pycompat.ispy3: 19 if pycompat.ispy3:
20 20
21 def set_noninheritable(fd): 21 def set_noninheritable(fd):
66 with os.fdopen( 66 with os.fdopen(
67 os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)), 67 os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)),
68 'w', 68 'w',
69 ) as write_result_f: 69 ) as write_result_f:
70 write_result_f.write(str(write_result)) 70 write_result_f.write(str(write_result))
71 '''
72
73
74 TEST_BROKEN_PIPE_CHILD_SCRIPT = r'''
75 import os
76 import pickle
77
78 from mercurial import dispatch
79 from mercurial.utils import procutil
80
81 dispatch.initstdio()
82 procutil.stdin.read(1) # wait until parent process closed pipe
83 try:
84 procutil.{stream}.write(b'test')
85 procutil.{stream}.flush()
86 except EnvironmentError as e:
87 with os.fdopen(
88 os.open(
89 {err_fn!r},
90 os.O_WRONLY
91 | getattr(os, 'O_BINARY', 0)
92 | getattr(os, 'O_TEMPORARY', 0),
93 ),
94 'wb',
95 ) as err_f:
96 pickle.dump(e, err_f)
97 # Exit early to suppress further broken pipe errors at interpreter shutdown.
98 os._exit(0)
71 ''' 99 '''
72 100
73 101
74 @contextlib.contextmanager 102 @contextlib.contextmanager
75 def _closing(fds): 103 def _closing(fds):
146 stream, 174 stream,
147 rwpair_generator, 175 rwpair_generator,
148 check_output, 176 check_output,
149 python_args=[], 177 python_args=[],
150 post_child_check=None, 178 post_child_check=None,
179 stdin_generator=None,
151 ): 180 ):
152 assert stream in ('stdout', 'stderr') 181 assert stream in ('stdout', 'stderr')
153 with rwpair_generator() as (stream_receiver, child_stream), open( 182 if stdin_generator is None:
154 os.devnull, 'rb' 183 stdin_generator = open(os.devnull, 'rb')
155 ) as child_stdin: 184 with rwpair_generator() as (
185 stream_receiver,
186 child_stream,
187 ), stdin_generator as child_stdin:
156 proc = subprocess.Popen( 188 proc = subprocess.Popen(
157 [sys.executable] + python_args + ['-c', child_script], 189 [sys.executable] + python_args + ['-c', child_script],
158 stdin=child_stdin, 190 stdin=child_stdin,
159 stdout=child_stream if stream == 'stdout' else None, 191 stdout=child_stream if stream == 'stdout' else None,
160 stderr=child_stream if stream == 'stderr' else None, 192 stderr=child_stream if stream == 'stderr' else None,
293 self._test_large_write('stderr', _pipes, python_args=['-u']) 325 self._test_large_write('stderr', _pipes, python_args=['-u'])
294 326
295 def test_large_write_stderr_ptys_unbuffered(self): 327 def test_large_write_stderr_ptys_unbuffered(self):
296 self._test_large_write('stderr', _ptys, python_args=['-u']) 328 self._test_large_write('stderr', _ptys, python_args=['-u'])
297 329
330 def _test_broken_pipe(self, stream):
331 assert stream in ('stdout', 'stderr')
332
333 def check_output(stream_receiver, proc):
334 os.close(stream_receiver)
335 proc.stdin.write(b'x')
336 proc.stdin.close()
337
338 def post_child_check():
339 err = util.pickle.load(err_f)
340 self.assertEqual(err.errno, errno.EPIPE)
341 self.assertEqual(err.strerror, "Broken pipe")
342
343 with tempfile.NamedTemporaryFile('rb') as err_f:
344 self._test(
345 TEST_BROKEN_PIPE_CHILD_SCRIPT.format(
346 stream=stream, err_fn=err_f.name
347 ),
348 stream,
349 _pipes,
350 check_output,
351 post_child_check=post_child_check,
352 stdin_generator=util.nullcontextmanager(subprocess.PIPE),
353 )
354
355 def test_broken_pipe_stdout(self):
356 self._test_broken_pipe('stdout')
357
358 def test_broken_pipe_stderr(self):
359 self._test_broken_pipe('stderr')
360
298 361
299 if __name__ == '__main__': 362 if __name__ == '__main__':
300 import silenttestrunner 363 import silenttestrunner
301 364
302 silenttestrunner.main(__name__) 365 silenttestrunner.main(__name__)