Mercurial > public > mercurial-scm > hg
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__) |