Show More
@@ -119,18 +119,25 b' if pycompat.ispy3:' | |||||
119 | # a silly wrapper to make a bytes stream backed by a unicode one. |
|
119 | # a silly wrapper to make a bytes stream backed by a unicode one. | |
120 | stdin = sys.stdin.buffer |
|
120 | stdin = sys.stdin.buffer | |
121 | stdout = _make_write_all(sys.stdout.buffer) |
|
121 | stdout = _make_write_all(sys.stdout.buffer) | |
|
122 | stderr = _make_write_all(sys.stderr.buffer) | |||
|
123 | if pycompat.iswindows: | |||
|
124 | # Work around Windows bugs. | |||
|
125 | stdout = platform.winstdout(stdout) | |||
|
126 | stderr = platform.winstdout(stderr) | |||
122 | if isatty(stdout): |
|
127 | if isatty(stdout): | |
123 | # The standard library doesn't offer line-buffered binary streams. |
|
128 | # The standard library doesn't offer line-buffered binary streams. | |
124 | stdout = make_line_buffered(stdout) |
|
129 | stdout = make_line_buffered(stdout) | |
125 | stderr = _make_write_all(sys.stderr.buffer) |
|
|||
126 | else: |
|
130 | else: | |
127 | # Python 2 uses the I/O streams provided by the C library. |
|
131 | # Python 2 uses the I/O streams provided by the C library. | |
128 | stdin = sys.stdin |
|
132 | stdin = sys.stdin | |
129 | stdout = sys.stdout |
|
133 | stdout = sys.stdout | |
|
134 | stderr = sys.stderr | |||
|
135 | if pycompat.iswindows: | |||
|
136 | # Work around Windows bugs. | |||
|
137 | stdout = platform.winstdout(stdout) | |||
|
138 | stderr = platform.winstdout(stderr) | |||
130 | if isatty(stdout): |
|
139 | if isatty(stdout): | |
131 | if pycompat.iswindows: |
|
140 | if pycompat.iswindows: | |
132 | # Work around size limit when writing to console. |
|
|||
133 | stdout = platform.winstdout(stdout) |
|
|||
134 | # The Windows C runtime library doesn't support line buffering. |
|
141 | # The Windows C runtime library doesn't support line buffering. | |
135 | stdout = make_line_buffered(stdout) |
|
142 | stdout = make_line_buffered(stdout) | |
136 | else: |
|
143 | else: | |
@@ -138,7 +145,6 b' else:' | |||||
138 | # replace a TTY destined stdout with a pipe destined stdout (e.g. |
|
145 | # replace a TTY destined stdout with a pipe destined stdout (e.g. | |
139 | # pager), we want line buffering. |
|
146 | # pager), we want line buffering. | |
140 | stdout = os.fdopen(stdout.fileno(), 'wb', 1) |
|
147 | stdout = os.fdopen(stdout.fileno(), 'wb', 1) | |
141 | stderr = sys.stderr |
|
|||
142 |
|
148 | |||
143 |
|
149 | |||
144 | findexe = platform.findexe |
|
150 | findexe = platform.findexe |
@@ -197,6 +197,7 b' class winstdout(object):' | |||||
197 |
|
197 | |||
198 | def __init__(self, fp): |
|
198 | def __init__(self, fp): | |
199 | self.fp = fp |
|
199 | self.fp = fp | |
|
200 | self.throttle = not pycompat.ispy3 and fp.isatty() | |||
200 |
|
201 | |||
201 | def __getattr__(self, key): |
|
202 | def __getattr__(self, key): | |
202 | return getattr(self.fp, key) |
|
203 | return getattr(self.fp, key) | |
@@ -208,13 +209,16 b' class winstdout(object):' | |||||
208 | pass |
|
209 | pass | |
209 |
|
210 | |||
210 | def write(self, s): |
|
211 | def write(self, s): | |
|
212 | if not pycompat.ispy3: | |||
|
213 | self.softspace = 0 | |||
211 | try: |
|
214 | try: | |
|
215 | if not self.throttle: | |||
|
216 | return self.fp.write(s) | |||
212 | # This is workaround for "Not enough space" error on |
|
217 | # This is workaround for "Not enough space" error on | |
213 | # writing large size of data to console. |
|
218 | # writing large size of data to console. | |
214 | limit = 16000 |
|
219 | limit = 16000 | |
215 | l = len(s) |
|
220 | l = len(s) | |
216 | start = 0 |
|
221 | start = 0 | |
217 | self.softspace = 0 |
|
|||
218 | while start < l: |
|
222 | while start < l: | |
219 | end = start + limit |
|
223 | end = start + limit | |
220 | self.fp.write(s[start:end]) |
|
224 | self.fp.write(s[start:end]) |
@@ -13,7 +13,7 b' 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: | |
@@ -71,6 +71,34 b' with os.fdopen(' | |||||
71 | ''' |
|
71 | ''' | |
72 |
|
72 | |||
73 |
|
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) | |||
|
99 | ''' | |||
|
100 | ||||
|
101 | ||||
74 | @contextlib.contextmanager |
|
102 | @contextlib.contextmanager | |
75 | def _closing(fds): |
|
103 | def _closing(fds): | |
76 | try: |
|
104 | try: | |
@@ -148,11 +176,15 b' class TestStdio(unittest.TestCase):' | |||||
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, | |
@@ -295,6 +327,37 b' class TestStdio(unittest.TestCase):' | |||||
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 |
General Comments 0
You need to be logged in to leave comments.
Login now