##// END OF EJS Templates
tests: check that procutil.std{out,err}.write() returns correct result...
Manuel Jacob -
r45657:dff20839 default
parent child Browse files
Show More
@@ -1,245 +1,277 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
3 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
4 """
4 """
5 from __future__ import absolute_import
5 from __future__ import absolute_import
6
6
7 import contextlib
7 import contextlib
8 import errno
8 import errno
9 import os
9 import os
10 import signal
10 import signal
11 import subprocess
11 import subprocess
12 import sys
12 import sys
13 import tempfile
13 import unittest
14 import unittest
14
15
15 from mercurial import pycompat
16 from mercurial import pycompat
16
17
17
18
18 TEST_BUFFERING_CHILD_SCRIPT = r'''
19 TEST_BUFFERING_CHILD_SCRIPT = r'''
19 import os
20 import os
20
21
21 from mercurial import dispatch
22 from mercurial import dispatch
22 from mercurial.utils import procutil
23 from mercurial.utils import procutil
23
24
24 dispatch.initstdio()
25 dispatch.initstdio()
25 procutil.{stream}.write(b'aaa')
26 procutil.{stream}.write(b'aaa')
26 os.write(procutil.{stream}.fileno(), b'[written aaa]')
27 os.write(procutil.{stream}.fileno(), b'[written aaa]')
27 procutil.{stream}.write(b'bbb\n')
28 procutil.{stream}.write(b'bbb\n')
28 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
29 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
29 '''
30 '''
30 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
31 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
31 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
32 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
32 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
33 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
33
34
34
35
35 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
36 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
36 import signal
37 import signal
37 import sys
38 import sys
38
39
39 from mercurial import dispatch
40 from mercurial import dispatch
40 from mercurial.utils import procutil
41 from mercurial.utils import procutil
41
42
42 signal.signal(signal.SIGINT, lambda *x: None)
43 signal.signal(signal.SIGINT, lambda *x: None)
43 dispatch.initstdio()
44 dispatch.initstdio()
44 procutil.{stream}.write(b'x' * 1048576)
45 write_result = procutil.{stream}.write(b'x' * 1048576)
46 with open({write_result_fn}, 'w') as write_result_f:
47 write_result_f.write(str(write_result))
45 '''
48 '''
46
49
47
50
48 @contextlib.contextmanager
51 @contextlib.contextmanager
49 def _closing(fds):
52 def _closing(fds):
50 try:
53 try:
51 yield
54 yield
52 finally:
55 finally:
53 for fd in fds:
56 for fd in fds:
54 try:
57 try:
55 os.close(fd)
58 os.close(fd)
56 except EnvironmentError:
59 except EnvironmentError:
57 pass
60 pass
58
61
59
62
60 @contextlib.contextmanager
63 @contextlib.contextmanager
61 def _devnull():
64 def _devnull():
62 devnull = os.open(os.devnull, os.O_WRONLY)
65 devnull = os.open(os.devnull, os.O_WRONLY)
63 with _closing([devnull]):
66 with _closing([devnull]):
64 yield (None, devnull)
67 yield (None, devnull)
65
68
66
69
67 @contextlib.contextmanager
70 @contextlib.contextmanager
68 def _pipes():
71 def _pipes():
69 rwpair = os.pipe()
72 rwpair = os.pipe()
70 with _closing(rwpair):
73 with _closing(rwpair):
71 yield rwpair
74 yield rwpair
72
75
73
76
74 @contextlib.contextmanager
77 @contextlib.contextmanager
75 def _ptys():
78 def _ptys():
76 if pycompat.iswindows:
79 if pycompat.iswindows:
77 raise unittest.SkipTest("PTYs are not supported on Windows")
80 raise unittest.SkipTest("PTYs are not supported on Windows")
78 import pty
81 import pty
79 import tty
82 import tty
80
83
81 rwpair = pty.openpty()
84 rwpair = pty.openpty()
82 with _closing(rwpair):
85 with _closing(rwpair):
83 tty.setraw(rwpair[0])
86 tty.setraw(rwpair[0])
84 yield rwpair
87 yield rwpair
85
88
86
89
87 def _readall(fd, buffer_size, initial_buf=None):
90 def _readall(fd, buffer_size, initial_buf=None):
88 buf = initial_buf or []
91 buf = initial_buf or []
89 while True:
92 while True:
90 try:
93 try:
91 s = os.read(fd, buffer_size)
94 s = os.read(fd, buffer_size)
92 except OSError as e:
95 except OSError as e:
93 if e.errno == errno.EIO:
96 if e.errno == errno.EIO:
94 # If the child-facing PTY got closed, reading from the
97 # If the child-facing PTY got closed, reading from the
95 # parent-facing PTY raises EIO.
98 # parent-facing PTY raises EIO.
96 break
99 break
97 raise
100 raise
98 if not s:
101 if not s:
99 break
102 break
100 buf.append(s)
103 buf.append(s)
101 return b''.join(buf)
104 return b''.join(buf)
102
105
103
106
104 class TestStdio(unittest.TestCase):
107 class TestStdio(unittest.TestCase):
105 def _test(
108 def _test(
106 self,
109 self,
107 child_script,
110 child_script,
108 stream,
111 stream,
109 rwpair_generator,
112 rwpair_generator,
110 check_output,
113 check_output,
111 python_args=[],
114 python_args=[],
115 post_child_check=None,
112 ):
116 ):
113 assert stream in ('stdout', 'stderr')
117 assert stream in ('stdout', 'stderr')
114 with rwpair_generator() as (stream_receiver, child_stream), open(
118 with rwpair_generator() as (stream_receiver, child_stream), open(
115 os.devnull, 'rb'
119 os.devnull, 'rb'
116 ) as child_stdin:
120 ) as child_stdin:
117 proc = subprocess.Popen(
121 proc = subprocess.Popen(
118 [sys.executable] + python_args + ['-c', child_script],
122 [sys.executable] + python_args + ['-c', child_script],
119 stdin=child_stdin,
123 stdin=child_stdin,
120 stdout=child_stream if stream == 'stdout' else None,
124 stdout=child_stream if stream == 'stdout' else None,
121 stderr=child_stream if stream == 'stderr' else None,
125 stderr=child_stream if stream == 'stderr' else None,
122 )
126 )
123 try:
127 try:
124 os.close(child_stream)
128 os.close(child_stream)
125 if stream_receiver is not None:
129 if stream_receiver is not None:
126 check_output(stream_receiver, proc)
130 check_output(stream_receiver, proc)
127 except: # re-raises
131 except: # re-raises
128 proc.terminate()
132 proc.terminate()
129 raise
133 raise
130 finally:
134 finally:
131 retcode = proc.wait()
135 retcode = proc.wait()
132 self.assertEqual(retcode, 0)
136 self.assertEqual(retcode, 0)
137 if post_child_check is not None:
138 post_child_check()
133
139
134 def _test_buffering(
140 def _test_buffering(
135 self, stream, rwpair_generator, expected_output, python_args=[]
141 self, stream, rwpair_generator, expected_output, python_args=[]
136 ):
142 ):
137 def check_output(stream_receiver, proc):
143 def check_output(stream_receiver, proc):
138 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
144 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
139
145
140 self._test(
146 self._test(
141 TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
147 TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
142 stream,
148 stream,
143 rwpair_generator,
149 rwpair_generator,
144 check_output,
150 check_output,
145 python_args,
151 python_args,
146 )
152 )
147
153
148 def test_buffering_stdout_devnull(self):
154 def test_buffering_stdout_devnull(self):
149 self._test_buffering('stdout', _devnull, None)
155 self._test_buffering('stdout', _devnull, None)
150
156
151 def test_buffering_stdout_pipes(self):
157 def test_buffering_stdout_pipes(self):
152 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
158 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
153
159
154 def test_buffering_stdout_ptys(self):
160 def test_buffering_stdout_ptys(self):
155 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
161 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
156
162
157 def test_buffering_stdout_devnull_unbuffered(self):
163 def test_buffering_stdout_devnull_unbuffered(self):
158 self._test_buffering('stdout', _devnull, None, python_args=['-u'])
164 self._test_buffering('stdout', _devnull, None, python_args=['-u'])
159
165
160 def test_buffering_stdout_pipes_unbuffered(self):
166 def test_buffering_stdout_pipes_unbuffered(self):
161 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
167 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
162
168
163 def test_buffering_stdout_ptys_unbuffered(self):
169 def test_buffering_stdout_ptys_unbuffered(self):
164 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
170 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
165
171
166 if not pycompat.ispy3 and not pycompat.iswindows:
172 if not pycompat.ispy3 and not pycompat.iswindows:
167 # On Python 2 on non-Windows, we manually open stdout in line-buffered
173 # On Python 2 on non-Windows, we manually open stdout in line-buffered
168 # mode if connected to a TTY. We should check if Python was configured
174 # mode if connected to a TTY. We should check if Python was configured
169 # to use unbuffered stdout, but it's hard to do that.
175 # to use unbuffered stdout, but it's hard to do that.
170 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
176 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
171 test_buffering_stdout_ptys_unbuffered
177 test_buffering_stdout_ptys_unbuffered
172 )
178 )
173
179
174 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
180 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
175 if not pycompat.ispy3 and pycompat.isdarwin:
181 if not pycompat.ispy3 and pycompat.isdarwin:
176 # Python 2 doesn't always retry on EINTR, but the libc might retry.
182 # Python 2 doesn't always retry on EINTR, but the libc might retry.
177 # So far, it was observed only on macOS that EINTR is raised at the
183 # So far, it was observed only on macOS that EINTR is raised at the
178 # Python level. As Python 2 support will be dropped soon-ish, we
184 # Python level. As Python 2 support will be dropped soon-ish, we
179 # won't attempt to fix it.
185 # won't attempt to fix it.
180 raise unittest.SkipTest("raises EINTR on macOS")
186 raise unittest.SkipTest("raises EINTR on macOS")
181
187
182 def check_output(stream_receiver, proc):
188 def check_output(stream_receiver, proc):
183 if not pycompat.iswindows:
189 if not pycompat.iswindows:
184 # On Unix, we can provoke a partial write() by interrupting it
190 # On Unix, we can provoke a partial write() by interrupting it
185 # by a signal handler as soon as a bit of data was written.
191 # by a signal handler as soon as a bit of data was written.
186 # We test that write() is called until all data is written.
192 # We test that write() is called until all data is written.
187 buf = [os.read(stream_receiver, 1)]
193 buf = [os.read(stream_receiver, 1)]
188 proc.send_signal(signal.SIGINT)
194 proc.send_signal(signal.SIGINT)
189 else:
195 else:
190 # On Windows, there doesn't seem to be a way to cause partial
196 # On Windows, there doesn't seem to be a way to cause partial
191 # writes.
197 # writes.
192 buf = []
198 buf = []
193 self.assertEqual(
199 self.assertEqual(
194 _readall(stream_receiver, 131072, buf), b'x' * 1048576
200 _readall(stream_receiver, 131072, buf), b'x' * 1048576
195 )
201 )
196
202
197 self._test(
203 def post_child_check():
198 TEST_LARGE_WRITE_CHILD_SCRIPT.format(stream=stream),
204 with open(write_result_fn, 'r') as write_result_f:
199 stream,
205 write_result_str = write_result_f.read()
200 rwpair_generator,
206 if pycompat.ispy3:
201 check_output,
207 # On Python 3, we test that the correct number of bytes is
202 python_args,
208 # claimed to have been written.
203 )
209 expected_write_result_str = '1048576'
210 else:
211 # On Python 2, we only check that the large write does not
212 # crash.
213 expected_write_result_str = 'None'
214 self.assertEqual(write_result_str, expected_write_result_str)
215
216 try:
217 # tempfile.mktemp() is unsafe in general, as a malicious process
218 # could create the file before we do. But in tests, we're running
219 # in a controlled environment.
220 write_result_fn = tempfile.mktemp()
221 self._test(
222 TEST_LARGE_WRITE_CHILD_SCRIPT.format(
223 stream=stream, write_result_fn=repr(write_result_fn)
224 ),
225 stream,
226 rwpair_generator,
227 check_output,
228 python_args,
229 post_child_check=post_child_check,
230 )
231 finally:
232 try:
233 os.unlink(write_result_fn)
234 except OSError:
235 pass
204
236
205 def test_large_write_stdout_devnull(self):
237 def test_large_write_stdout_devnull(self):
206 self._test_large_write('stdout', _devnull)
238 self._test_large_write('stdout', _devnull)
207
239
208 def test_large_write_stdout_pipes(self):
240 def test_large_write_stdout_pipes(self):
209 self._test_large_write('stdout', _pipes)
241 self._test_large_write('stdout', _pipes)
210
242
211 def test_large_write_stdout_ptys(self):
243 def test_large_write_stdout_ptys(self):
212 self._test_large_write('stdout', _ptys)
244 self._test_large_write('stdout', _ptys)
213
245
214 def test_large_write_stdout_devnull_unbuffered(self):
246 def test_large_write_stdout_devnull_unbuffered(self):
215 self._test_large_write('stdout', _devnull, python_args=['-u'])
247 self._test_large_write('stdout', _devnull, python_args=['-u'])
216
248
217 def test_large_write_stdout_pipes_unbuffered(self):
249 def test_large_write_stdout_pipes_unbuffered(self):
218 self._test_large_write('stdout', _pipes, python_args=['-u'])
250 self._test_large_write('stdout', _pipes, python_args=['-u'])
219
251
220 def test_large_write_stdout_ptys_unbuffered(self):
252 def test_large_write_stdout_ptys_unbuffered(self):
221 self._test_large_write('stdout', _ptys, python_args=['-u'])
253 self._test_large_write('stdout', _ptys, python_args=['-u'])
222
254
223 def test_large_write_stderr_devnull(self):
255 def test_large_write_stderr_devnull(self):
224 self._test_large_write('stderr', _devnull)
256 self._test_large_write('stderr', _devnull)
225
257
226 def test_large_write_stderr_pipes(self):
258 def test_large_write_stderr_pipes(self):
227 self._test_large_write('stderr', _pipes)
259 self._test_large_write('stderr', _pipes)
228
260
229 def test_large_write_stderr_ptys(self):
261 def test_large_write_stderr_ptys(self):
230 self._test_large_write('stderr', _ptys)
262 self._test_large_write('stderr', _ptys)
231
263
232 def test_large_write_stderr_devnull_unbuffered(self):
264 def test_large_write_stderr_devnull_unbuffered(self):
233 self._test_large_write('stderr', _devnull, python_args=['-u'])
265 self._test_large_write('stderr', _devnull, python_args=['-u'])
234
266
235 def test_large_write_stderr_pipes_unbuffered(self):
267 def test_large_write_stderr_pipes_unbuffered(self):
236 self._test_large_write('stderr', _pipes, python_args=['-u'])
268 self._test_large_write('stderr', _pipes, python_args=['-u'])
237
269
238 def test_large_write_stderr_ptys_unbuffered(self):
270 def test_large_write_stderr_ptys_unbuffered(self):
239 self._test_large_write('stderr', _ptys, python_args=['-u'])
271 self._test_large_write('stderr', _ptys, python_args=['-u'])
240
272
241
273
242 if __name__ == '__main__':
274 if __name__ == '__main__':
243 import silenttestrunner
275 import silenttestrunner
244
276
245 silenttestrunner.main(__name__)
277 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now