##// END OF EJS Templates
procutil: ensure that procutil.std{out,err}.write() writes all bytes...
Manuel Jacob -
r45655:8e046070 default
parent child Browse files
Show More
@@ -80,16 +80,49 b' def make_line_buffered(stream):'
80 return LineBufferedWrapper(stream)
80 return LineBufferedWrapper(stream)
81
81
82
82
83 class WriteAllWrapper(object):
84 def __init__(self, orig):
85 self.orig = orig
86
87 def __getattr__(self, attr):
88 return getattr(self.orig, attr)
89
90 def write(self, s):
91 write1 = self.orig.write
92 m = memoryview(s)
93 total_to_write = len(s)
94 total_written = 0
95 while total_written < total_to_write:
96 total_written += write1(m[total_written:])
97 return total_written
98
99
100 io.IOBase.register(WriteAllWrapper)
101
102
103 def make_write_all(stream):
104 assert pycompat.ispy3
105 if isinstance(stream, WriteAllWrapper):
106 return stream
107 if isinstance(stream, io.BufferedIOBase):
108 # The io.BufferedIOBase.write() contract guarantees that all data is
109 # written.
110 return stream
111 # In general, the write() method of streams is free to write only part of
112 # the data.
113 return WriteAllWrapper(stream)
114
115
83 if pycompat.ispy3:
116 if pycompat.ispy3:
84 # Python 3 implements its own I/O streams.
117 # Python 3 implements its own I/O streams.
85 # TODO: .buffer might not exist if std streams were replaced; we'll need
118 # TODO: .buffer might not exist if std streams were replaced; we'll need
86 # 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.
87 stdin = sys.stdin.buffer
120 stdin = sys.stdin.buffer
88 stdout = sys.stdout.buffer
121 stdout = make_write_all(sys.stdout.buffer)
89 if isatty(stdout):
122 if isatty(stdout):
90 # The standard library doesn't offer line-buffered binary streams.
123 # The standard library doesn't offer line-buffered binary streams.
91 stdout = make_line_buffered(stdout)
124 stdout = make_line_buffered(stdout)
92 stderr = sys.stderr.buffer
125 stderr = make_write_all(sys.stderr.buffer)
93 else:
126 else:
94 # Python 2 uses the I/O streams provided by the C library.
127 # Python 2 uses the I/O streams provided by the C library.
95 stdin = sys.stdin
128 stdin = sys.stdin
@@ -7,6 +7,7 b' from __future__ import absolute_import'
7 import contextlib
7 import contextlib
8 import errno
8 import errno
9 import os
9 import os
10 import signal
10 import subprocess
11 import subprocess
11 import sys
12 import sys
12 import unittest
13 import unittest
@@ -31,6 +32,19 b" LINE_BUFFERED = b'[written aaa]aaabbb\\n["
31 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
32 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
32
33
33
34
35 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
36 import signal
37 import sys
38
39 from mercurial import dispatch
40 from mercurial.utils import procutil
41
42 signal.signal(signal.SIGINT, lambda *x: None)
43 dispatch.initstdio()
44 procutil.{stream}.write(b'x' * 1048576)
45 '''
46
47
34 @contextlib.contextmanager
48 @contextlib.contextmanager
35 def _closing(fds):
49 def _closing(fds):
36 try:
50 try:
@@ -63,8 +77,8 b' def _ptys():'
63 yield rwpair
77 yield rwpair
64
78
65
79
66 def _readall(fd, buffer_size):
80 def _readall(fd, buffer_size, initial_buf=None):
67 buf = []
81 buf = initial_buf or []
68 while True:
82 while True:
69 try:
83 try:
70 s = os.read(fd, buffer_size)
84 s = os.read(fd, buffer_size)
@@ -101,7 +115,7 b' class TestStdio(unittest.TestCase):'
101 )
115 )
102 try:
116 try:
103 os.close(child_stream)
117 os.close(child_stream)
104 check_output(stream_receiver)
118 check_output(stream_receiver, proc)
105 except: # re-raises
119 except: # re-raises
106 proc.terminate()
120 proc.terminate()
107 raise
121 raise
@@ -112,7 +126,7 b' class TestStdio(unittest.TestCase):'
112 def _test_buffering(
126 def _test_buffering(
113 self, stream, rwpair_generator, expected_output, python_args=[]
127 self, stream, rwpair_generator, expected_output, python_args=[]
114 ):
128 ):
115 def check_output(stream_receiver):
129 def check_output(stream_receiver, proc):
116 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
130 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
117
131
118 self._test(
132 self._test(
@@ -143,6 +157,61 b' class TestStdio(unittest.TestCase):'
143 test_buffering_stdout_ptys_unbuffered
157 test_buffering_stdout_ptys_unbuffered
144 )
158 )
145
159
160 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
161 if not pycompat.ispy3 and pycompat.isdarwin:
162 # Python 2 doesn't always retry on EINTR, but the libc might retry.
163 # So far, it was observed only on macOS that EINTR is raised at the
164 # Python level. As Python 2 support will be dropped soon-ish, we
165 # won't attempt to fix it.
166 raise unittest.SkipTest("raises EINTR on macOS")
167
168 def check_output(stream_receiver, proc):
169 if not pycompat.iswindows:
170 # On Unix, we can provoke a partial write() by interrupting it
171 # by a signal handler as soon as a bit of data was written.
172 # We test that write() is called until all data is written.
173 buf = [os.read(stream_receiver, 1)]
174 proc.send_signal(signal.SIGINT)
175 else:
176 # On Windows, there doesn't seem to be a way to cause partial
177 # writes.
178 buf = []
179 self.assertEqual(
180 _readall(stream_receiver, 131072, buf), b'x' * 1048576
181 )
182
183 self._test(
184 TEST_LARGE_WRITE_CHILD_SCRIPT.format(stream=stream),
185 stream,
186 rwpair_generator,
187 check_output,
188 python_args,
189 )
190
191 def test_large_write_stdout_pipes(self):
192 self._test_large_write('stdout', _pipes)
193
194 def test_large_write_stdout_ptys(self):
195 self._test_large_write('stdout', _ptys)
196
197 def test_large_write_stdout_pipes_unbuffered(self):
198 self._test_large_write('stdout', _pipes, python_args=['-u'])
199
200 def test_large_write_stdout_ptys_unbuffered(self):
201 self._test_large_write('stdout', _ptys, python_args=['-u'])
202
203 def test_large_write_stderr_pipes(self):
204 self._test_large_write('stderr', _pipes)
205
206 def test_large_write_stderr_ptys(self):
207 self._test_large_write('stderr', _ptys)
208
209 def test_large_write_stderr_pipes_unbuffered(self):
210 self._test_large_write('stderr', _pipes, python_args=['-u'])
211
212 def test_large_write_stderr_ptys_unbuffered(self):
213 self._test_large_write('stderr', _ptys, python_args=['-u'])
214
146
215
147 if __name__ == '__main__':
216 if __name__ == '__main__':
148 import silenttestrunner
217 import silenttestrunner
General Comments 0
You need to be logged in to leave comments. Login now