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