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