##// END OF EJS Templates
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
Manuel Jacob -
r45708:a37f290a default
parent child Browse files
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