Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/IPython/utils/_process_posix.py: 46%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

83 statements  

1"""Posix-specific implementation of process utilities. 

2 

3This file is only meant to be imported by process.py, not by end-users. 

4""" 

5 

6#----------------------------------------------------------------------------- 

7# Copyright (C) 2010-2011 The IPython Development Team 

8# 

9# Distributed under the terms of the BSD License. The full license is in 

10# the file COPYING, distributed as part of this software. 

11#----------------------------------------------------------------------------- 

12 

13#----------------------------------------------------------------------------- 

14# Imports 

15#----------------------------------------------------------------------------- 

16 

17# Stdlib 

18import errno 

19import os 

20import subprocess as sp 

21import sys 

22 

23# Our own 

24from ._process_common import getoutput, arg_split 

25from IPython.utils.encoding import DEFAULT_ENCODING 

26 

27#----------------------------------------------------------------------------- 

28# Function definitions 

29#----------------------------------------------------------------------------- 

30 

31class ProcessHandler(object): 

32 """Execute subprocesses under the control of pexpect. 

33 """ 

34 # Timeout in seconds to wait on each reading of the subprocess' output. 

35 # This should not be set too low to avoid cpu overusage from our side, 

36 # since we read in a loop whose period is controlled by this timeout. 

37 read_timeout = 0.05 

38 

39 # Timeout to give a process if we receive SIGINT, between sending the 

40 # SIGINT to the process and forcefully terminating it. 

41 terminate_timeout = 0.2 

42 

43 # File object where stdout and stderr of the subprocess will be written 

44 logfile = None 

45 

46 # Shell to call for subprocesses to execute 

47 _sh = None 

48 

49 @property 

50 def sh(self): 

51 if self._sh is None: 

52 import pexpect 

53 shell_name = os.environ.get("SHELL", "sh") 

54 self._sh = pexpect.which(shell_name) 

55 if self._sh is None: 

56 raise OSError('"{}" shell not found'.format(shell_name)) 

57 

58 return self._sh 

59 

60 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None): 

61 """Arguments are used for pexpect calls.""" 

62 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is 

63 None else read_timeout) 

64 self.terminate_timeout = (ProcessHandler.terminate_timeout if 

65 terminate_timeout is None else 

66 terminate_timeout) 

67 self.logfile = sys.stdout if logfile is None else logfile 

68 

69 def getoutput(self, cmd): 

70 """Run a command and return its stdout/stderr as a string. 

71 

72 Parameters 

73 ---------- 

74 cmd : str 

75 A command to be executed in the system shell. 

76 

77 Returns 

78 ------- 

79 output : str 

80 A string containing the combination of stdout and stderr from the 

81 subprocess, in whatever order the subprocess originally wrote to its 

82 file descriptors (so the order of the information in this string is the 

83 correct order as would be seen if running the command in a terminal). 

84 """ 

85 import pexpect 

86 try: 

87 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n') 

88 except KeyboardInterrupt: 

89 print('^C', file=sys.stderr, end='') 

90 

91 def getoutput_pexpect(self, cmd): 

92 """Run a command and return its stdout/stderr as a string. 

93 

94 Parameters 

95 ---------- 

96 cmd : str 

97 A command to be executed in the system shell. 

98 

99 Returns 

100 ------- 

101 output : str 

102 A string containing the combination of stdout and stderr from the 

103 subprocess, in whatever order the subprocess originally wrote to its 

104 file descriptors (so the order of the information in this string is the 

105 correct order as would be seen if running the command in a terminal). 

106 """ 

107 import pexpect 

108 try: 

109 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n') 

110 except KeyboardInterrupt: 

111 print('^C', file=sys.stderr, end='') 

112 

113 def system(self, cmd): 

114 """Execute a command in a subshell. 

115 

116 Parameters 

117 ---------- 

118 cmd : str 

119 A command to be executed in the system shell. 

120 

121 Returns 

122 ------- 

123 int : child's exitstatus 

124 """ 

125 import pexpect 

126 

127 # Get likely encoding for the output. 

128 enc = DEFAULT_ENCODING 

129 

130 # Patterns to match on the output, for pexpect. We read input and 

131 # allow either a short timeout or EOF 

132 patterns = [pexpect.TIMEOUT, pexpect.EOF] 

133 # the index of the EOF pattern in the list. 

134 # even though we know it's 1, this call means we don't have to worry if 

135 # we change the above list, and forget to change this value: 

136 EOF_index = patterns.index(pexpect.EOF) 

137 # The size of the output stored so far in the process output buffer. 

138 # Since pexpect only appends to this buffer, each time we print we 

139 # record how far we've printed, so that next time we only print *new* 

140 # content from the buffer. 

141 out_size = 0 

142 try: 

143 # Since we're not really searching the buffer for text patterns, we 

144 # can set pexpect's search window to be tiny and it won't matter. 

145 # We only search for the 'patterns' timeout or EOF, which aren't in 

146 # the text itself. 

147 #child = pexpect.spawn(pcmd, searchwindowsize=1) 

148 if hasattr(pexpect, 'spawnb'): 

149 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U 

150 else: 

151 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect 

152 flush = sys.stdout.flush 

153 while True: 

154 # res is the index of the pattern that caused the match, so we 

155 # know whether we've finished (if we matched EOF) or not 

156 res_idx = child.expect_list(patterns, self.read_timeout) 

157 print(child.before[out_size:].decode(enc, 'replace'), end='') 

158 flush() 

159 if res_idx==EOF_index: 

160 break 

161 # Update the pointer to what we've already printed 

162 out_size = len(child.before) 

163 except KeyboardInterrupt: 

164 # We need to send ^C to the process. The ascii code for '^C' is 3 

165 # (the character is known as ETX for 'End of Text', see 

166 # curses.ascii.ETX). 

167 child.sendline(chr(3)) 

168 # Read and print any more output the program might produce on its 

169 # way out. 

170 try: 

171 out_size = len(child.before) 

172 child.expect_list(patterns, self.terminate_timeout) 

173 print(child.before[out_size:].decode(enc, 'replace'), end='') 

174 sys.stdout.flush() 

175 except KeyboardInterrupt: 

176 # Impatient users tend to type it multiple times 

177 pass 

178 finally: 

179 # Ensure the subprocess really is terminated 

180 child.terminate(force=True) 

181 # add isalive check, to ensure exitstatus is set: 

182 child.isalive() 

183 

184 # We follow the subprocess pattern, returning either the exit status 

185 # as a positive number, or the terminating signal as a negative 

186 # number. 

187 # on Linux, sh returns 128+n for signals terminating child processes on Linux 

188 # on BSD (OS X), the signal code is set instead 

189 if child.exitstatus is None: 

190 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None 

191 if child.signalstatus is None: 

192 # this condition may never occur, 

193 # but let's be certain we always return an integer. 

194 return 0 

195 return -child.signalstatus 

196 if child.exitstatus > 128: 

197 return -(child.exitstatus - 128) 

198 return child.exitstatus 

199 

200 

201# Make system() with a functional interface for outside use. Note that we use 

202# getoutput() from the _common utils, which is built on top of popen(). Using 

203# pexpect to get subprocess output produces difficult to parse output, since 

204# programs think they are talking to a tty and produce highly formatted output 

205# (ls is a good example) that makes them hard. 

206system = ProcessHandler().system 

207 

208def check_pid(pid): 

209 try: 

210 os.kill(pid, 0) 

211 except OSError as err: 

212 if err.errno == errno.ESRCH: 

213 return False 

214 elif err.errno == errno.EPERM: 

215 # Don't have permission to signal the process - probably means it exists 

216 return True 

217 raise 

218 else: 

219 return True