##// END OF EJS Templates
procutil: make mercurial.utils.procutil.stderr unbuffered...
Manuel Jacob -
r45590:8403cc54 default
parent child Browse files
Show More
@@ -1,711 +1,723 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import threading
19 import threading
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from ..pycompat import (
23 from ..pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 # Import like this to keep import-checker happy
35 # Import like this to keep import-checker happy
36 from ..utils import resourceutil
36 from ..utils import resourceutil
37
37
38 osutil = policy.importmod('osutil')
38 osutil = policy.importmod('osutil')
39
39
40 if pycompat.iswindows:
40 if pycompat.iswindows:
41 from .. import windows as platform
41 from .. import windows as platform
42 else:
42 else:
43 from .. import posix as platform
43 from .. import posix as platform
44
44
45
45
46 def isatty(fp):
46 def isatty(fp):
47 try:
47 try:
48 return fp.isatty()
48 return fp.isatty()
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52
52
53 class LineBufferedWrapper(object):
53 class LineBufferedWrapper(object):
54 def __init__(self, orig):
54 def __init__(self, orig):
55 self.orig = orig
55 self.orig = orig
56
56
57 def __getattr__(self, attr):
57 def __getattr__(self, attr):
58 return getattr(self.orig, attr)
58 return getattr(self.orig, attr)
59
59
60 def write(self, s):
60 def write(self, s):
61 orig = self.orig
61 orig = self.orig
62 res = orig.write(s)
62 res = orig.write(s)
63 if s.endswith(b'\n'):
63 if s.endswith(b'\n'):
64 orig.flush()
64 orig.flush()
65 return res
65 return res
66
66
67
67
68 io.BufferedIOBase.register(LineBufferedWrapper)
68 io.BufferedIOBase.register(LineBufferedWrapper)
69
69
70
70
71 def make_line_buffered(stream):
71 def make_line_buffered(stream):
72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
73 # On Python 3, buffered streams can be expected to subclass
73 # On Python 3, buffered streams can be expected to subclass
74 # BufferedIOBase. This is definitively the case for the streams
74 # BufferedIOBase. This is definitively the case for the streams
75 # initialized by the interpreter. For unbuffered streams, we don't need
75 # initialized by the interpreter. For unbuffered streams, we don't need
76 # to emulate line buffering.
76 # to emulate line buffering.
77 return stream
77 return stream
78 if isinstance(stream, LineBufferedWrapper):
78 if isinstance(stream, LineBufferedWrapper):
79 return stream
79 return stream
80 return LineBufferedWrapper(stream)
80 return LineBufferedWrapper(stream)
81
81
82
82
83 stderr = pycompat.stderr
83 stderr = pycompat.stderr
84 stdin = pycompat.stdin
84 stdin = pycompat.stdin
85 stdout = pycompat.stdout
85 stdout = pycompat.stdout
86
86
87 if pycompat.iswindows:
87 if pycompat.iswindows:
88 stdout = platform.winstdout(stdout)
88 stdout = platform.winstdout(stdout)
89
89
90 # glibc determines buffering on first write to stdout - if we replace a TTY
90 # glibc determines buffering on first write to stdout - if we replace a TTY
91 # destined stdout with a pipe destined stdout (e.g. pager), we want line
91 # destined stdout with a pipe destined stdout (e.g. pager), we want line
92 # buffering.
92 # buffering.
93 if isatty(stdout):
93 if isatty(stdout):
94 if pycompat.ispy3 or pycompat.iswindows:
94 if pycompat.ispy3 or pycompat.iswindows:
95 # On Python 3, buffered binary streams can't be set line-buffered.
95 # On Python 3, buffered binary streams can't be set line-buffered.
96 # On Python 2, Windows doesn't support line buffering.
96 # On Python 2, Windows doesn't support line buffering.
97 # Therefore we have a wrapper that implements line buffering.
97 # Therefore we have a wrapper that implements line buffering.
98 stdout = make_line_buffered(stdout)
98 stdout = make_line_buffered(stdout)
99 else:
99 else:
100 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
100 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
101
101
102 # stderr should be unbuffered
103 if pycompat.ispy3:
104 # On Python 3, buffered streams may expose an underlying raw stream. This is
105 # definitively the case for the streams initialized by the interpreter. If
106 # the attribute isn't present, the stream is already unbuffered or doesn't
107 # expose an underlying raw stream, in which case we use the stream as-is.
108 stderr = getattr(stderr, 'raw', stderr)
109 elif pycompat.iswindows:
110 # On Windows, stderr is buffered at least when connected to a pipe.
111 stderr = os.fdopen(stderr.fileno(), 'wb', 0)
112 # On other platforms, stderr is always unbuffered.
113
102
114
103 findexe = platform.findexe
115 findexe = platform.findexe
104 _gethgcmd = platform.gethgcmd
116 _gethgcmd = platform.gethgcmd
105 getuser = platform.getuser
117 getuser = platform.getuser
106 getpid = os.getpid
118 getpid = os.getpid
107 hidewindow = platform.hidewindow
119 hidewindow = platform.hidewindow
108 readpipe = platform.readpipe
120 readpipe = platform.readpipe
109 setbinary = platform.setbinary
121 setbinary = platform.setbinary
110 setsignalhandler = platform.setsignalhandler
122 setsignalhandler = platform.setsignalhandler
111 shellquote = platform.shellquote
123 shellquote = platform.shellquote
112 shellsplit = platform.shellsplit
124 shellsplit = platform.shellsplit
113 spawndetached = platform.spawndetached
125 spawndetached = platform.spawndetached
114 sshargs = platform.sshargs
126 sshargs = platform.sshargs
115 testpid = platform.testpid
127 testpid = platform.testpid
116
128
117 try:
129 try:
118 setprocname = osutil.setprocname
130 setprocname = osutil.setprocname
119 except AttributeError:
131 except AttributeError:
120 pass
132 pass
121 try:
133 try:
122 unblocksignal = osutil.unblocksignal
134 unblocksignal = osutil.unblocksignal
123 except AttributeError:
135 except AttributeError:
124 pass
136 pass
125
137
126 closefds = pycompat.isposix
138 closefds = pycompat.isposix
127
139
128
140
129 def explainexit(code):
141 def explainexit(code):
130 """return a message describing a subprocess status
142 """return a message describing a subprocess status
131 (codes from kill are negative - not os.system/wait encoding)"""
143 (codes from kill are negative - not os.system/wait encoding)"""
132 if code >= 0:
144 if code >= 0:
133 return _(b"exited with status %d") % code
145 return _(b"exited with status %d") % code
134 return _(b"killed by signal %d") % -code
146 return _(b"killed by signal %d") % -code
135
147
136
148
137 class _pfile(object):
149 class _pfile(object):
138 """File-like wrapper for a stream opened by subprocess.Popen()"""
150 """File-like wrapper for a stream opened by subprocess.Popen()"""
139
151
140 def __init__(self, proc, fp):
152 def __init__(self, proc, fp):
141 self._proc = proc
153 self._proc = proc
142 self._fp = fp
154 self._fp = fp
143
155
144 def close(self):
156 def close(self):
145 # unlike os.popen(), this returns an integer in subprocess coding
157 # unlike os.popen(), this returns an integer in subprocess coding
146 self._fp.close()
158 self._fp.close()
147 return self._proc.wait()
159 return self._proc.wait()
148
160
149 def __iter__(self):
161 def __iter__(self):
150 return iter(self._fp)
162 return iter(self._fp)
151
163
152 def __getattr__(self, attr):
164 def __getattr__(self, attr):
153 return getattr(self._fp, attr)
165 return getattr(self._fp, attr)
154
166
155 def __enter__(self):
167 def __enter__(self):
156 return self
168 return self
157
169
158 def __exit__(self, exc_type, exc_value, exc_tb):
170 def __exit__(self, exc_type, exc_value, exc_tb):
159 self.close()
171 self.close()
160
172
161
173
162 def popen(cmd, mode=b'rb', bufsize=-1):
174 def popen(cmd, mode=b'rb', bufsize=-1):
163 if mode == b'rb':
175 if mode == b'rb':
164 return _popenreader(cmd, bufsize)
176 return _popenreader(cmd, bufsize)
165 elif mode == b'wb':
177 elif mode == b'wb':
166 return _popenwriter(cmd, bufsize)
178 return _popenwriter(cmd, bufsize)
167 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
179 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
168
180
169
181
170 def _popenreader(cmd, bufsize):
182 def _popenreader(cmd, bufsize):
171 p = subprocess.Popen(
183 p = subprocess.Popen(
172 tonativestr(cmd),
184 tonativestr(cmd),
173 shell=True,
185 shell=True,
174 bufsize=bufsize,
186 bufsize=bufsize,
175 close_fds=closefds,
187 close_fds=closefds,
176 stdout=subprocess.PIPE,
188 stdout=subprocess.PIPE,
177 )
189 )
178 return _pfile(p, p.stdout)
190 return _pfile(p, p.stdout)
179
191
180
192
181 def _popenwriter(cmd, bufsize):
193 def _popenwriter(cmd, bufsize):
182 p = subprocess.Popen(
194 p = subprocess.Popen(
183 tonativestr(cmd),
195 tonativestr(cmd),
184 shell=True,
196 shell=True,
185 bufsize=bufsize,
197 bufsize=bufsize,
186 close_fds=closefds,
198 close_fds=closefds,
187 stdin=subprocess.PIPE,
199 stdin=subprocess.PIPE,
188 )
200 )
189 return _pfile(p, p.stdin)
201 return _pfile(p, p.stdin)
190
202
191
203
192 def popen2(cmd, env=None):
204 def popen2(cmd, env=None):
193 # Setting bufsize to -1 lets the system decide the buffer size.
205 # Setting bufsize to -1 lets the system decide the buffer size.
194 # The default for bufsize is 0, meaning unbuffered. This leads to
206 # The default for bufsize is 0, meaning unbuffered. This leads to
195 # poor performance on Mac OS X: http://bugs.python.org/issue4194
207 # poor performance on Mac OS X: http://bugs.python.org/issue4194
196 p = subprocess.Popen(
208 p = subprocess.Popen(
197 tonativestr(cmd),
209 tonativestr(cmd),
198 shell=True,
210 shell=True,
199 bufsize=-1,
211 bufsize=-1,
200 close_fds=closefds,
212 close_fds=closefds,
201 stdin=subprocess.PIPE,
213 stdin=subprocess.PIPE,
202 stdout=subprocess.PIPE,
214 stdout=subprocess.PIPE,
203 env=tonativeenv(env),
215 env=tonativeenv(env),
204 )
216 )
205 return p.stdin, p.stdout
217 return p.stdin, p.stdout
206
218
207
219
208 def popen3(cmd, env=None):
220 def popen3(cmd, env=None):
209 stdin, stdout, stderr, p = popen4(cmd, env)
221 stdin, stdout, stderr, p = popen4(cmd, env)
210 return stdin, stdout, stderr
222 return stdin, stdout, stderr
211
223
212
224
213 def popen4(cmd, env=None, bufsize=-1):
225 def popen4(cmd, env=None, bufsize=-1):
214 p = subprocess.Popen(
226 p = subprocess.Popen(
215 tonativestr(cmd),
227 tonativestr(cmd),
216 shell=True,
228 shell=True,
217 bufsize=bufsize,
229 bufsize=bufsize,
218 close_fds=closefds,
230 close_fds=closefds,
219 stdin=subprocess.PIPE,
231 stdin=subprocess.PIPE,
220 stdout=subprocess.PIPE,
232 stdout=subprocess.PIPE,
221 stderr=subprocess.PIPE,
233 stderr=subprocess.PIPE,
222 env=tonativeenv(env),
234 env=tonativeenv(env),
223 )
235 )
224 return p.stdin, p.stdout, p.stderr, p
236 return p.stdin, p.stdout, p.stderr, p
225
237
226
238
227 def pipefilter(s, cmd):
239 def pipefilter(s, cmd):
228 '''filter string S through command CMD, returning its output'''
240 '''filter string S through command CMD, returning its output'''
229 p = subprocess.Popen(
241 p = subprocess.Popen(
230 tonativestr(cmd),
242 tonativestr(cmd),
231 shell=True,
243 shell=True,
232 close_fds=closefds,
244 close_fds=closefds,
233 stdin=subprocess.PIPE,
245 stdin=subprocess.PIPE,
234 stdout=subprocess.PIPE,
246 stdout=subprocess.PIPE,
235 )
247 )
236 pout, perr = p.communicate(s)
248 pout, perr = p.communicate(s)
237 return pout
249 return pout
238
250
239
251
240 def tempfilter(s, cmd):
252 def tempfilter(s, cmd):
241 '''filter string S through a pair of temporary files with CMD.
253 '''filter string S through a pair of temporary files with CMD.
242 CMD is used as a template to create the real command to be run,
254 CMD is used as a template to create the real command to be run,
243 with the strings INFILE and OUTFILE replaced by the real names of
255 with the strings INFILE and OUTFILE replaced by the real names of
244 the temporary files generated.'''
256 the temporary files generated.'''
245 inname, outname = None, None
257 inname, outname = None, None
246 try:
258 try:
247 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
259 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
248 fp = os.fdopen(infd, 'wb')
260 fp = os.fdopen(infd, 'wb')
249 fp.write(s)
261 fp.write(s)
250 fp.close()
262 fp.close()
251 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
263 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
252 os.close(outfd)
264 os.close(outfd)
253 cmd = cmd.replace(b'INFILE', inname)
265 cmd = cmd.replace(b'INFILE', inname)
254 cmd = cmd.replace(b'OUTFILE', outname)
266 cmd = cmd.replace(b'OUTFILE', outname)
255 code = system(cmd)
267 code = system(cmd)
256 if pycompat.sysplatform == b'OpenVMS' and code & 1:
268 if pycompat.sysplatform == b'OpenVMS' and code & 1:
257 code = 0
269 code = 0
258 if code:
270 if code:
259 raise error.Abort(
271 raise error.Abort(
260 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
272 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
261 )
273 )
262 with open(outname, b'rb') as fp:
274 with open(outname, b'rb') as fp:
263 return fp.read()
275 return fp.read()
264 finally:
276 finally:
265 try:
277 try:
266 if inname:
278 if inname:
267 os.unlink(inname)
279 os.unlink(inname)
268 except OSError:
280 except OSError:
269 pass
281 pass
270 try:
282 try:
271 if outname:
283 if outname:
272 os.unlink(outname)
284 os.unlink(outname)
273 except OSError:
285 except OSError:
274 pass
286 pass
275
287
276
288
277 _filtertable = {
289 _filtertable = {
278 b'tempfile:': tempfilter,
290 b'tempfile:': tempfilter,
279 b'pipe:': pipefilter,
291 b'pipe:': pipefilter,
280 }
292 }
281
293
282
294
283 def filter(s, cmd):
295 def filter(s, cmd):
284 """filter a string through a command that transforms its input to its
296 """filter a string through a command that transforms its input to its
285 output"""
297 output"""
286 for name, fn in pycompat.iteritems(_filtertable):
298 for name, fn in pycompat.iteritems(_filtertable):
287 if cmd.startswith(name):
299 if cmd.startswith(name):
288 return fn(s, cmd[len(name) :].lstrip())
300 return fn(s, cmd[len(name) :].lstrip())
289 return pipefilter(s, cmd)
301 return pipefilter(s, cmd)
290
302
291
303
292 _hgexecutable = None
304 _hgexecutable = None
293
305
294
306
295 def hgexecutable():
307 def hgexecutable():
296 """return location of the 'hg' executable.
308 """return location of the 'hg' executable.
297
309
298 Defaults to $HG or 'hg' in the search path.
310 Defaults to $HG or 'hg' in the search path.
299 """
311 """
300 if _hgexecutable is None:
312 if _hgexecutable is None:
301 hg = encoding.environ.get(b'HG')
313 hg = encoding.environ.get(b'HG')
302 mainmod = sys.modules['__main__']
314 mainmod = sys.modules['__main__']
303 if hg:
315 if hg:
304 _sethgexecutable(hg)
316 _sethgexecutable(hg)
305 elif resourceutil.mainfrozen():
317 elif resourceutil.mainfrozen():
306 if getattr(sys, 'frozen', None) == 'macosx_app':
318 if getattr(sys, 'frozen', None) == 'macosx_app':
307 # Env variable set by py2app
319 # Env variable set by py2app
308 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
320 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
309 else:
321 else:
310 _sethgexecutable(pycompat.sysexecutable)
322 _sethgexecutable(pycompat.sysexecutable)
311 elif (
323 elif (
312 not pycompat.iswindows
324 not pycompat.iswindows
313 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
325 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
314 ):
326 ):
315 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
327 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
316 else:
328 else:
317 _sethgexecutable(
329 _sethgexecutable(
318 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
330 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
319 )
331 )
320 return _hgexecutable
332 return _hgexecutable
321
333
322
334
323 def _sethgexecutable(path):
335 def _sethgexecutable(path):
324 """set location of the 'hg' executable"""
336 """set location of the 'hg' executable"""
325 global _hgexecutable
337 global _hgexecutable
326 _hgexecutable = path
338 _hgexecutable = path
327
339
328
340
329 def _testfileno(f, stdf):
341 def _testfileno(f, stdf):
330 fileno = getattr(f, 'fileno', None)
342 fileno = getattr(f, 'fileno', None)
331 try:
343 try:
332 return fileno and fileno() == stdf.fileno()
344 return fileno and fileno() == stdf.fileno()
333 except io.UnsupportedOperation:
345 except io.UnsupportedOperation:
334 return False # fileno() raised UnsupportedOperation
346 return False # fileno() raised UnsupportedOperation
335
347
336
348
337 def isstdin(f):
349 def isstdin(f):
338 return _testfileno(f, sys.__stdin__)
350 return _testfileno(f, sys.__stdin__)
339
351
340
352
341 def isstdout(f):
353 def isstdout(f):
342 return _testfileno(f, sys.__stdout__)
354 return _testfileno(f, sys.__stdout__)
343
355
344
356
345 def protectstdio(uin, uout):
357 def protectstdio(uin, uout):
346 """Duplicate streams and redirect original if (uin, uout) are stdio
358 """Duplicate streams and redirect original if (uin, uout) are stdio
347
359
348 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
360 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
349 redirected to stderr so the output is still readable.
361 redirected to stderr so the output is still readable.
350
362
351 Returns (fin, fout) which point to the original (uin, uout) fds, but
363 Returns (fin, fout) which point to the original (uin, uout) fds, but
352 may be copy of (uin, uout). The returned streams can be considered
364 may be copy of (uin, uout). The returned streams can be considered
353 "owned" in that print(), exec(), etc. never reach to them.
365 "owned" in that print(), exec(), etc. never reach to them.
354 """
366 """
355 uout.flush()
367 uout.flush()
356 fin, fout = uin, uout
368 fin, fout = uin, uout
357 if _testfileno(uin, stdin):
369 if _testfileno(uin, stdin):
358 newfd = os.dup(uin.fileno())
370 newfd = os.dup(uin.fileno())
359 nullfd = os.open(os.devnull, os.O_RDONLY)
371 nullfd = os.open(os.devnull, os.O_RDONLY)
360 os.dup2(nullfd, uin.fileno())
372 os.dup2(nullfd, uin.fileno())
361 os.close(nullfd)
373 os.close(nullfd)
362 fin = os.fdopen(newfd, 'rb')
374 fin = os.fdopen(newfd, 'rb')
363 if _testfileno(uout, stdout):
375 if _testfileno(uout, stdout):
364 newfd = os.dup(uout.fileno())
376 newfd = os.dup(uout.fileno())
365 os.dup2(stderr.fileno(), uout.fileno())
377 os.dup2(stderr.fileno(), uout.fileno())
366 fout = os.fdopen(newfd, 'wb')
378 fout = os.fdopen(newfd, 'wb')
367 return fin, fout
379 return fin, fout
368
380
369
381
370 def restorestdio(uin, uout, fin, fout):
382 def restorestdio(uin, uout, fin, fout):
371 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
383 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
372 uout.flush()
384 uout.flush()
373 for f, uif in [(fin, uin), (fout, uout)]:
385 for f, uif in [(fin, uin), (fout, uout)]:
374 if f is not uif:
386 if f is not uif:
375 os.dup2(f.fileno(), uif.fileno())
387 os.dup2(f.fileno(), uif.fileno())
376 f.close()
388 f.close()
377
389
378
390
379 def shellenviron(environ=None):
391 def shellenviron(environ=None):
380 """return environ with optional override, useful for shelling out"""
392 """return environ with optional override, useful for shelling out"""
381
393
382 def py2shell(val):
394 def py2shell(val):
383 """convert python object into string that is useful to shell"""
395 """convert python object into string that is useful to shell"""
384 if val is None or val is False:
396 if val is None or val is False:
385 return b'0'
397 return b'0'
386 if val is True:
398 if val is True:
387 return b'1'
399 return b'1'
388 return pycompat.bytestr(val)
400 return pycompat.bytestr(val)
389
401
390 env = dict(encoding.environ)
402 env = dict(encoding.environ)
391 if environ:
403 if environ:
392 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
404 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
393 env[b'HG'] = hgexecutable()
405 env[b'HG'] = hgexecutable()
394 return env
406 return env
395
407
396
408
397 if pycompat.iswindows:
409 if pycompat.iswindows:
398
410
399 def shelltonative(cmd, env):
411 def shelltonative(cmd, env):
400 return platform.shelltocmdexe( # pytype: disable=module-attr
412 return platform.shelltocmdexe( # pytype: disable=module-attr
401 cmd, shellenviron(env)
413 cmd, shellenviron(env)
402 )
414 )
403
415
404 tonativestr = encoding.strfromlocal
416 tonativestr = encoding.strfromlocal
405 else:
417 else:
406
418
407 def shelltonative(cmd, env):
419 def shelltonative(cmd, env):
408 return cmd
420 return cmd
409
421
410 tonativestr = pycompat.identity
422 tonativestr = pycompat.identity
411
423
412
424
413 def tonativeenv(env):
425 def tonativeenv(env):
414 '''convert the environment from bytes to strings suitable for Popen(), etc.
426 '''convert the environment from bytes to strings suitable for Popen(), etc.
415 '''
427 '''
416 return pycompat.rapply(tonativestr, env)
428 return pycompat.rapply(tonativestr, env)
417
429
418
430
419 def system(cmd, environ=None, cwd=None, out=None):
431 def system(cmd, environ=None, cwd=None, out=None):
420 '''enhanced shell command execution.
432 '''enhanced shell command execution.
421 run with environment maybe modified, maybe in different dir.
433 run with environment maybe modified, maybe in different dir.
422
434
423 if out is specified, it is assumed to be a file-like object that has a
435 if out is specified, it is assumed to be a file-like object that has a
424 write() method. stdout and stderr will be redirected to out.'''
436 write() method. stdout and stderr will be redirected to out.'''
425 try:
437 try:
426 stdout.flush()
438 stdout.flush()
427 except Exception:
439 except Exception:
428 pass
440 pass
429 env = shellenviron(environ)
441 env = shellenviron(environ)
430 if out is None or isstdout(out):
442 if out is None or isstdout(out):
431 rc = subprocess.call(
443 rc = subprocess.call(
432 tonativestr(cmd),
444 tonativestr(cmd),
433 shell=True,
445 shell=True,
434 close_fds=closefds,
446 close_fds=closefds,
435 env=tonativeenv(env),
447 env=tonativeenv(env),
436 cwd=pycompat.rapply(tonativestr, cwd),
448 cwd=pycompat.rapply(tonativestr, cwd),
437 )
449 )
438 else:
450 else:
439 proc = subprocess.Popen(
451 proc = subprocess.Popen(
440 tonativestr(cmd),
452 tonativestr(cmd),
441 shell=True,
453 shell=True,
442 close_fds=closefds,
454 close_fds=closefds,
443 env=tonativeenv(env),
455 env=tonativeenv(env),
444 cwd=pycompat.rapply(tonativestr, cwd),
456 cwd=pycompat.rapply(tonativestr, cwd),
445 stdout=subprocess.PIPE,
457 stdout=subprocess.PIPE,
446 stderr=subprocess.STDOUT,
458 stderr=subprocess.STDOUT,
447 )
459 )
448 for line in iter(proc.stdout.readline, b''):
460 for line in iter(proc.stdout.readline, b''):
449 out.write(line)
461 out.write(line)
450 proc.wait()
462 proc.wait()
451 rc = proc.returncode
463 rc = proc.returncode
452 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
464 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
453 rc = 0
465 rc = 0
454 return rc
466 return rc
455
467
456
468
457 _is_gui = None
469 _is_gui = None
458
470
459
471
460 def _gui():
472 def _gui():
461 '''Are we running in a GUI?'''
473 '''Are we running in a GUI?'''
462 if pycompat.isdarwin:
474 if pycompat.isdarwin:
463 if b'SSH_CONNECTION' in encoding.environ:
475 if b'SSH_CONNECTION' in encoding.environ:
464 # handle SSH access to a box where the user is logged in
476 # handle SSH access to a box where the user is logged in
465 return False
477 return False
466 elif getattr(osutil, 'isgui', None):
478 elif getattr(osutil, 'isgui', None):
467 # check if a CoreGraphics session is available
479 # check if a CoreGraphics session is available
468 return osutil.isgui()
480 return osutil.isgui()
469 else:
481 else:
470 # pure build; use a safe default
482 # pure build; use a safe default
471 return True
483 return True
472 else:
484 else:
473 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
485 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
474
486
475
487
476 def gui():
488 def gui():
477 global _is_gui
489 global _is_gui
478 if _is_gui is None:
490 if _is_gui is None:
479 _is_gui = _gui()
491 _is_gui = _gui()
480 return _is_gui
492 return _is_gui
481
493
482
494
483 def hgcmd():
495 def hgcmd():
484 """Return the command used to execute current hg
496 """Return the command used to execute current hg
485
497
486 This is different from hgexecutable() because on Windows we want
498 This is different from hgexecutable() because on Windows we want
487 to avoid things opening new shell windows like batch files, so we
499 to avoid things opening new shell windows like batch files, so we
488 get either the python call or current executable.
500 get either the python call or current executable.
489 """
501 """
490 if resourceutil.mainfrozen():
502 if resourceutil.mainfrozen():
491 if getattr(sys, 'frozen', None) == 'macosx_app':
503 if getattr(sys, 'frozen', None) == 'macosx_app':
492 # Env variable set by py2app
504 # Env variable set by py2app
493 return [encoding.environ[b'EXECUTABLEPATH']]
505 return [encoding.environ[b'EXECUTABLEPATH']]
494 else:
506 else:
495 return [pycompat.sysexecutable]
507 return [pycompat.sysexecutable]
496 return _gethgcmd()
508 return _gethgcmd()
497
509
498
510
499 def rundetached(args, condfn):
511 def rundetached(args, condfn):
500 """Execute the argument list in a detached process.
512 """Execute the argument list in a detached process.
501
513
502 condfn is a callable which is called repeatedly and should return
514 condfn is a callable which is called repeatedly and should return
503 True once the child process is known to have started successfully.
515 True once the child process is known to have started successfully.
504 At this point, the child process PID is returned. If the child
516 At this point, the child process PID is returned. If the child
505 process fails to start or finishes before condfn() evaluates to
517 process fails to start or finishes before condfn() evaluates to
506 True, return -1.
518 True, return -1.
507 """
519 """
508 # Windows case is easier because the child process is either
520 # Windows case is easier because the child process is either
509 # successfully starting and validating the condition or exiting
521 # successfully starting and validating the condition or exiting
510 # on failure. We just poll on its PID. On Unix, if the child
522 # on failure. We just poll on its PID. On Unix, if the child
511 # process fails to start, it will be left in a zombie state until
523 # process fails to start, it will be left in a zombie state until
512 # the parent wait on it, which we cannot do since we expect a long
524 # the parent wait on it, which we cannot do since we expect a long
513 # running process on success. Instead we listen for SIGCHLD telling
525 # running process on success. Instead we listen for SIGCHLD telling
514 # us our child process terminated.
526 # us our child process terminated.
515 terminated = set()
527 terminated = set()
516
528
517 def handler(signum, frame):
529 def handler(signum, frame):
518 terminated.add(os.wait())
530 terminated.add(os.wait())
519
531
520 prevhandler = None
532 prevhandler = None
521 SIGCHLD = getattr(signal, 'SIGCHLD', None)
533 SIGCHLD = getattr(signal, 'SIGCHLD', None)
522 if SIGCHLD is not None:
534 if SIGCHLD is not None:
523 prevhandler = signal.signal(SIGCHLD, handler)
535 prevhandler = signal.signal(SIGCHLD, handler)
524 try:
536 try:
525 pid = spawndetached(args)
537 pid = spawndetached(args)
526 while not condfn():
538 while not condfn():
527 if (pid in terminated or not testpid(pid)) and not condfn():
539 if (pid in terminated or not testpid(pid)) and not condfn():
528 return -1
540 return -1
529 time.sleep(0.1)
541 time.sleep(0.1)
530 return pid
542 return pid
531 finally:
543 finally:
532 if prevhandler is not None:
544 if prevhandler is not None:
533 signal.signal(signal.SIGCHLD, prevhandler)
545 signal.signal(signal.SIGCHLD, prevhandler)
534
546
535
547
536 @contextlib.contextmanager
548 @contextlib.contextmanager
537 def uninterruptible(warn):
549 def uninterruptible(warn):
538 """Inhibit SIGINT handling on a region of code.
550 """Inhibit SIGINT handling on a region of code.
539
551
540 Note that if this is called in a non-main thread, it turns into a no-op.
552 Note that if this is called in a non-main thread, it turns into a no-op.
541
553
542 Args:
554 Args:
543 warn: A callable which takes no arguments, and returns True if the
555 warn: A callable which takes no arguments, and returns True if the
544 previous signal handling should be restored.
556 previous signal handling should be restored.
545 """
557 """
546
558
547 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
559 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
548 shouldbail = []
560 shouldbail = []
549
561
550 def disabledsiginthandler(*args):
562 def disabledsiginthandler(*args):
551 if warn():
563 if warn():
552 signal.signal(signal.SIGINT, oldsiginthandler[0])
564 signal.signal(signal.SIGINT, oldsiginthandler[0])
553 del oldsiginthandler[0]
565 del oldsiginthandler[0]
554 shouldbail.append(True)
566 shouldbail.append(True)
555
567
556 try:
568 try:
557 try:
569 try:
558 signal.signal(signal.SIGINT, disabledsiginthandler)
570 signal.signal(signal.SIGINT, disabledsiginthandler)
559 except ValueError:
571 except ValueError:
560 # wrong thread, oh well, we tried
572 # wrong thread, oh well, we tried
561 del oldsiginthandler[0]
573 del oldsiginthandler[0]
562 yield
574 yield
563 finally:
575 finally:
564 if oldsiginthandler:
576 if oldsiginthandler:
565 signal.signal(signal.SIGINT, oldsiginthandler[0])
577 signal.signal(signal.SIGINT, oldsiginthandler[0])
566 if shouldbail:
578 if shouldbail:
567 raise KeyboardInterrupt
579 raise KeyboardInterrupt
568
580
569
581
570 if pycompat.iswindows:
582 if pycompat.iswindows:
571 # no fork on Windows, but we can create a detached process
583 # no fork on Windows, but we can create a detached process
572 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
584 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
573 # No stdlib constant exists for this value
585 # No stdlib constant exists for this value
574 DETACHED_PROCESS = 0x00000008
586 DETACHED_PROCESS = 0x00000008
575 # Following creation flags might create a console GUI window.
587 # Following creation flags might create a console GUI window.
576 # Using subprocess.CREATE_NEW_CONSOLE might helps.
588 # Using subprocess.CREATE_NEW_CONSOLE might helps.
577 # See https://phab.mercurial-scm.org/D1701 for discussion
589 # See https://phab.mercurial-scm.org/D1701 for discussion
578 _creationflags = (
590 _creationflags = (
579 DETACHED_PROCESS
591 DETACHED_PROCESS
580 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
592 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
581 )
593 )
582
594
583 def runbgcommand(
595 def runbgcommand(
584 script,
596 script,
585 env,
597 env,
586 shell=False,
598 shell=False,
587 stdout=None,
599 stdout=None,
588 stderr=None,
600 stderr=None,
589 ensurestart=True,
601 ensurestart=True,
590 record_wait=None,
602 record_wait=None,
591 ):
603 ):
592 '''Spawn a command without waiting for it to finish.'''
604 '''Spawn a command without waiting for it to finish.'''
593 # we can't use close_fds *and* redirect stdin. I'm not sure that we
605 # we can't use close_fds *and* redirect stdin. I'm not sure that we
594 # need to because the detached process has no console connection.
606 # need to because the detached process has no console connection.
595 p = subprocess.Popen(
607 p = subprocess.Popen(
596 tonativestr(script),
608 tonativestr(script),
597 shell=shell,
609 shell=shell,
598 env=tonativeenv(env),
610 env=tonativeenv(env),
599 close_fds=True,
611 close_fds=True,
600 creationflags=_creationflags,
612 creationflags=_creationflags,
601 stdout=stdout,
613 stdout=stdout,
602 stderr=stderr,
614 stderr=stderr,
603 )
615 )
604 if record_wait is not None:
616 if record_wait is not None:
605 record_wait(p.wait)
617 record_wait(p.wait)
606
618
607
619
608 else:
620 else:
609
621
610 def runbgcommand(
622 def runbgcommand(
611 cmd,
623 cmd,
612 env,
624 env,
613 shell=False,
625 shell=False,
614 stdout=None,
626 stdout=None,
615 stderr=None,
627 stderr=None,
616 ensurestart=True,
628 ensurestart=True,
617 record_wait=None,
629 record_wait=None,
618 ):
630 ):
619 '''Spawn a command without waiting for it to finish.
631 '''Spawn a command without waiting for it to finish.
620
632
621
633
622 When `record_wait` is not None, the spawned process will not be fully
634 When `record_wait` is not None, the spawned process will not be fully
623 detached and the `record_wait` argument will be called with a the
635 detached and the `record_wait` argument will be called with a the
624 `Subprocess.wait` function for the spawned process. This is mostly
636 `Subprocess.wait` function for the spawned process. This is mostly
625 useful for developers that need to make sure the spawned process
637 useful for developers that need to make sure the spawned process
626 finished before a certain point. (eg: writing test)'''
638 finished before a certain point. (eg: writing test)'''
627 if pycompat.isdarwin:
639 if pycompat.isdarwin:
628 # avoid crash in CoreFoundation in case another thread
640 # avoid crash in CoreFoundation in case another thread
629 # calls gui() while we're calling fork().
641 # calls gui() while we're calling fork().
630 gui()
642 gui()
631
643
632 # double-fork to completely detach from the parent process
644 # double-fork to completely detach from the parent process
633 # based on http://code.activestate.com/recipes/278731
645 # based on http://code.activestate.com/recipes/278731
634 if record_wait is None:
646 if record_wait is None:
635 pid = os.fork()
647 pid = os.fork()
636 if pid:
648 if pid:
637 if not ensurestart:
649 if not ensurestart:
638 # Even though we're not waiting on the child process,
650 # Even though we're not waiting on the child process,
639 # we still must call waitpid() on it at some point so
651 # we still must call waitpid() on it at some point so
640 # it's not a zombie/defunct. This is especially relevant for
652 # it's not a zombie/defunct. This is especially relevant for
641 # chg since the parent process won't die anytime soon.
653 # chg since the parent process won't die anytime soon.
642 # We use a thread to make the overhead tiny.
654 # We use a thread to make the overhead tiny.
643 def _do_wait():
655 def _do_wait():
644 os.waitpid(pid, 0)
656 os.waitpid(pid, 0)
645
657
646 t = threading.Thread(target=_do_wait)
658 t = threading.Thread(target=_do_wait)
647 t.daemon = True
659 t.daemon = True
648 t.start()
660 t.start()
649 return
661 return
650 # Parent process
662 # Parent process
651 (_pid, status) = os.waitpid(pid, 0)
663 (_pid, status) = os.waitpid(pid, 0)
652 if os.WIFEXITED(status):
664 if os.WIFEXITED(status):
653 returncode = os.WEXITSTATUS(status)
665 returncode = os.WEXITSTATUS(status)
654 else:
666 else:
655 returncode = -(os.WTERMSIG(status))
667 returncode = -(os.WTERMSIG(status))
656 if returncode != 0:
668 if returncode != 0:
657 # The child process's return code is 0 on success, an errno
669 # The child process's return code is 0 on success, an errno
658 # value on failure, or 255 if we don't have a valid errno
670 # value on failure, or 255 if we don't have a valid errno
659 # value.
671 # value.
660 #
672 #
661 # (It would be slightly nicer to return the full exception info
673 # (It would be slightly nicer to return the full exception info
662 # over a pipe as the subprocess module does. For now it
674 # over a pipe as the subprocess module does. For now it
663 # doesn't seem worth adding that complexity here, though.)
675 # doesn't seem worth adding that complexity here, though.)
664 if returncode == 255:
676 if returncode == 255:
665 returncode = errno.EINVAL
677 returncode = errno.EINVAL
666 raise OSError(
678 raise OSError(
667 returncode,
679 returncode,
668 b'error running %r: %s'
680 b'error running %r: %s'
669 % (cmd, os.strerror(returncode)),
681 % (cmd, os.strerror(returncode)),
670 )
682 )
671 return
683 return
672
684
673 returncode = 255
685 returncode = 255
674 try:
686 try:
675 if record_wait is None:
687 if record_wait is None:
676 # Start a new session
688 # Start a new session
677 os.setsid()
689 os.setsid()
678
690
679 stdin = open(os.devnull, b'r')
691 stdin = open(os.devnull, b'r')
680 if stdout is None:
692 if stdout is None:
681 stdout = open(os.devnull, b'w')
693 stdout = open(os.devnull, b'w')
682 if stderr is None:
694 if stderr is None:
683 stderr = open(os.devnull, b'w')
695 stderr = open(os.devnull, b'w')
684
696
685 # connect stdin to devnull to make sure the subprocess can't
697 # connect stdin to devnull to make sure the subprocess can't
686 # muck up that stream for mercurial.
698 # muck up that stream for mercurial.
687 p = subprocess.Popen(
699 p = subprocess.Popen(
688 cmd,
700 cmd,
689 shell=shell,
701 shell=shell,
690 env=env,
702 env=env,
691 close_fds=True,
703 close_fds=True,
692 stdin=stdin,
704 stdin=stdin,
693 stdout=stdout,
705 stdout=stdout,
694 stderr=stderr,
706 stderr=stderr,
695 )
707 )
696 if record_wait is not None:
708 if record_wait is not None:
697 record_wait(p.wait)
709 record_wait(p.wait)
698 returncode = 0
710 returncode = 0
699 except EnvironmentError as ex:
711 except EnvironmentError as ex:
700 returncode = ex.errno & 0xFF
712 returncode = ex.errno & 0xFF
701 if returncode == 0:
713 if returncode == 0:
702 # This shouldn't happen, but just in case make sure the
714 # This shouldn't happen, but just in case make sure the
703 # return code is never 0 here.
715 # return code is never 0 here.
704 returncode = 255
716 returncode = 255
705 except Exception:
717 except Exception:
706 returncode = 255
718 returncode = 255
707 finally:
719 finally:
708 # mission accomplished, this child needs to exit and not
720 # mission accomplished, this child needs to exit and not
709 # continue the hg process here.
721 # continue the hg process here.
710 if record_wait is None:
722 if record_wait is None:
711 os._exit(returncode)
723 os._exit(returncode)
@@ -1,107 +1,119 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 os
8 import os
9 import subprocess
9 import subprocess
10 import sys
10 import sys
11 import unittest
11 import unittest
12
12
13 from mercurial import pycompat
13 from mercurial import pycompat
14
14
15
15
16 CHILD_PROCESS = r'''
16 CHILD_PROCESS = r'''
17 import os
17 import os
18
18
19 from mercurial import dispatch
19 from mercurial import dispatch
20 from mercurial.utils import procutil
20 from mercurial.utils import procutil
21
21
22 dispatch.initstdio()
22 dispatch.initstdio()
23 procutil.{stream}.write(b'aaa')
23 procutil.{stream}.write(b'aaa')
24 os.write(procutil.{stream}.fileno(), b'[written aaa]')
24 os.write(procutil.{stream}.fileno(), b'[written aaa]')
25 procutil.{stream}.write(b'bbb\n')
25 procutil.{stream}.write(b'bbb\n')
26 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
26 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
27 '''
27 '''
28 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
28 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
29 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
29 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
30 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
30 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
31
31
32
32
33 @contextlib.contextmanager
33 @contextlib.contextmanager
34 def _closing(fds):
34 def _closing(fds):
35 try:
35 try:
36 yield
36 yield
37 finally:
37 finally:
38 for fd in fds:
38 for fd in fds:
39 try:
39 try:
40 os.close(fd)
40 os.close(fd)
41 except EnvironmentError:
41 except EnvironmentError:
42 pass
42 pass
43
43
44
44
45 @contextlib.contextmanager
45 @contextlib.contextmanager
46 def _pipes():
46 def _pipes():
47 rwpair = os.pipe()
47 rwpair = os.pipe()
48 with _closing(rwpair):
48 with _closing(rwpair):
49 yield rwpair
49 yield rwpair
50
50
51
51
52 @contextlib.contextmanager
52 @contextlib.contextmanager
53 def _ptys():
53 def _ptys():
54 if pycompat.iswindows:
54 if pycompat.iswindows:
55 raise unittest.SkipTest("PTYs are not supported on Windows")
55 raise unittest.SkipTest("PTYs are not supported on Windows")
56 import pty
56 import pty
57 import tty
57 import tty
58
58
59 rwpair = pty.openpty()
59 rwpair = pty.openpty()
60 with _closing(rwpair):
60 with _closing(rwpair):
61 tty.setraw(rwpair[0])
61 tty.setraw(rwpair[0])
62 yield rwpair
62 yield rwpair
63
63
64
64
65 class TestStdio(unittest.TestCase):
65 class TestStdio(unittest.TestCase):
66 def _test(self, stream, rwpair_generator, expected_output, python_args=[]):
66 def _test(self, stream, rwpair_generator, expected_output, python_args=[]):
67 assert stream in ('stdout', 'stderr')
67 assert stream in ('stdout', 'stderr')
68 with rwpair_generator() as (stream_receiver, child_stream), open(
68 with rwpair_generator() as (stream_receiver, child_stream), open(
69 os.devnull, 'rb'
69 os.devnull, 'rb'
70 ) as child_stdin:
70 ) as child_stdin:
71 proc = subprocess.Popen(
71 proc = subprocess.Popen(
72 [sys.executable]
72 [sys.executable]
73 + python_args
73 + python_args
74 + ['-c', CHILD_PROCESS.format(stream=stream)],
74 + ['-c', CHILD_PROCESS.format(stream=stream)],
75 stdin=child_stdin,
75 stdin=child_stdin,
76 stdout=child_stream if stream == 'stdout' else None,
76 stdout=child_stream if stream == 'stdout' else None,
77 stderr=child_stream if stream == 'stderr' else None,
77 stderr=child_stream if stream == 'stderr' else None,
78 )
78 )
79 retcode = proc.wait()
79 retcode = proc.wait()
80 self.assertEqual(retcode, 0)
80 self.assertEqual(retcode, 0)
81 self.assertEqual(os.read(stream_receiver, 1024), expected_output)
81 self.assertEqual(os.read(stream_receiver, 1024), expected_output)
82
82
83 def test_stdout_pipes(self):
83 def test_stdout_pipes(self):
84 self._test('stdout', _pipes, FULLY_BUFFERED)
84 self._test('stdout', _pipes, FULLY_BUFFERED)
85
85
86 def test_stdout_ptys(self):
86 def test_stdout_ptys(self):
87 self._test('stdout', _ptys, LINE_BUFFERED)
87 self._test('stdout', _ptys, LINE_BUFFERED)
88
88
89 def test_stdout_pipes_unbuffered(self):
89 def test_stdout_pipes_unbuffered(self):
90 self._test('stdout', _pipes, UNBUFFERED, python_args=['-u'])
90 self._test('stdout', _pipes, UNBUFFERED, python_args=['-u'])
91
91
92 def test_stdout_ptys_unbuffered(self):
92 def test_stdout_ptys_unbuffered(self):
93 self._test('stdout', _ptys, UNBUFFERED, python_args=['-u'])
93 self._test('stdout', _ptys, UNBUFFERED, python_args=['-u'])
94
94
95 if not pycompat.ispy3 and not pycompat.iswindows:
95 if not pycompat.ispy3 and not pycompat.iswindows:
96 # On Python 2 on non-Windows, we manually open stdout in line-buffered
96 # On Python 2 on non-Windows, we manually open stdout in line-buffered
97 # mode if connected to a TTY. We should check if Python was configured
97 # mode if connected to a TTY. We should check if Python was configured
98 # to use unbuffered stdout, but it's hard to do that.
98 # to use unbuffered stdout, but it's hard to do that.
99 test_stdout_ptys_unbuffered = unittest.expectedFailure(
99 test_stdout_ptys_unbuffered = unittest.expectedFailure(
100 test_stdout_ptys_unbuffered
100 test_stdout_ptys_unbuffered
101 )
101 )
102
102
103 def test_stderr_pipes(self):
104 self._test('stderr', _pipes, UNBUFFERED)
105
106 def test_stderr_ptys(self):
107 self._test('stderr', _ptys, UNBUFFERED)
108
109 def test_stderr_pipes_unbuffered(self):
110 self._test('stderr', _pipes, UNBUFFERED, python_args=['-u'])
111
112 def test_stderr_ptys_unbuffered(self):
113 self._test('stderr', _ptys, UNBUFFERED, python_args=['-u'])
114
103
115
104 if __name__ == '__main__':
116 if __name__ == '__main__':
105 import silenttestrunner
117 import silenttestrunner
106
118
107 silenttestrunner.main(__name__)
119 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now