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

79 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

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 

23import pexpect 

24 

25# Our own 

26from ._process_common import getoutput, arg_split 

27from IPython.utils.encoding import DEFAULT_ENCODING 

28 

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

30# Function definitions 

31#----------------------------------------------------------------------------- 

32 

33class ProcessHandler(object): 

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

35 """ 

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

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

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

39 read_timeout = 0.05 

40 

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

42 # SIGINT to the process and forcefully terminating it. 

43 terminate_timeout = 0.2 

44 

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

46 logfile = None 

47 

48 # Shell to call for subprocesses to execute 

49 _sh = None 

50 

51 @property 

52 def sh(self): 

53 if self._sh is None: 

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

55 self._sh = pexpect.which(shell_name) 

56 if self._sh is None: 

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

58 

59 return self._sh 

60 

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

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

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

64 None else read_timeout) 

65 self.terminate_timeout = (ProcessHandler.terminate_timeout if 

66 terminate_timeout is None else 

67 terminate_timeout) 

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

69 

70 def getoutput(self, cmd): 

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

72 

73 Parameters 

74 ---------- 

75 cmd : str 

76 A command to be executed in the system shell. 

77 

78 Returns 

79 ------- 

80 output : str 

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

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

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

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

85 """ 

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 try: 

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

109 except KeyboardInterrupt: 

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

111 

112 def system(self, cmd): 

113 """Execute a command in a subshell. 

114 

115 Parameters 

116 ---------- 

117 cmd : str 

118 A command to be executed in the system shell. 

119 

120 Returns 

121 ------- 

122 int : child's exitstatus 

123 """ 

124 # Get likely encoding for the output. 

125 enc = DEFAULT_ENCODING 

126 

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

128 # allow either a short timeout or EOF 

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

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

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

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

133 EOF_index = patterns.index(pexpect.EOF) 

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

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

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

137 # content from the buffer. 

138 out_size = 0 

139 try: 

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

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

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

143 # the text itself. 

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

145 if hasattr(pexpect, 'spawnb'): 

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

147 else: 

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

149 flush = sys.stdout.flush 

150 while True: 

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

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

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

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

155 flush() 

156 if res_idx==EOF_index: 

157 break 

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

159 out_size = len(child.before) 

160 except KeyboardInterrupt: 

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

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

163 # curses.ascii.ETX). 

164 child.sendline(chr(3)) 

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

166 # way out. 

167 try: 

168 out_size = len(child.before) 

169 child.expect_list(patterns, self.terminate_timeout) 

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

171 sys.stdout.flush() 

172 except KeyboardInterrupt: 

173 # Impatient users tend to type it multiple times 

174 pass 

175 finally: 

176 # Ensure the subprocess really is terminated 

177 child.terminate(force=True) 

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

179 child.isalive() 

180 

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

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

183 # number. 

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

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

186 if child.exitstatus is None: 

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

188 if child.signalstatus is None: 

189 # this condition may never occur, 

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

191 return 0 

192 return -child.signalstatus 

193 if child.exitstatus > 128: 

194 return -(child.exitstatus - 128) 

195 return child.exitstatus 

196 

197 

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

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

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

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

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

203system = ProcessHandler().system 

204 

205def check_pid(pid): 

206 try: 

207 os.kill(pid, 0) 

208 except OSError as err: 

209 if err.errno == errno.ESRCH: 

210 return False 

211 elif err.errno == errno.EPERM: 

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

213 return True 

214 raise 

215 else: 

216 return True