Coverage for src/robotide/contrib/testrunner/Process.py: 54%

114 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2010 Orbitz WorldWide 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15# Modified by NSN 

16# Copyright 2010-2012 Nokia Solutions and Networks 

17# Copyright 2013-2015 Nokia Networks 

18# Copyright 2016- Robot Framework Foundation 

19# 

20# Licensed under the Apache License, Version 2.0 (the "License"); 

21# you may not use this file except in compliance with the License. 

22# You may obtain a copy of the License at 

23# 

24# http://www.apache.org/licenses/LICENSE-2.0 

25# 

26# Unless required by applicable law or agreed to in writing, software 

27# distributed under the License is distributed on an "AS IS" BASIS, 

28# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

29# See the License for the specific language governing permissions and 

30# limitations under the License. 

31 

32import os 

33import signal 

34import socket 

35import subprocess 

36import sys 

37import threading 

38 

39from queue import Empty, Queue 

40from robotide.context import IS_WINDOWS 

41 

42OUTPUT_ENCODING = sys.getfilesystemencoding() 

43 

44 

45class Process(object): 

46 

47 def __init__(self, cwd): 

48 self._process = None 1ac

49 self._error_stream = None 1ac

50 self._output_stream = None 1ac

51 self._cwd = cwd 1ac

52 self._port = None 1ac

53 self._sock = None 1ac

54 self._kill_called = False 1ac

55 

56 def run_command(self, command): 

57 # We need to supply stdin for subprocess, because other ways in python 

58 # subprocess will try using sys.stdin which causes an error in windows 

59 # print("DEBUG: enter run_command %s dir %s" % (command, self._cwd)) 

60 subprocess_args = dict(bufsize=0, 1ac

61 stdout=subprocess.PIPE, 

62 stderr=subprocess.PIPE, 

63 stdin=subprocess.PIPE, 

64 cwd=self._cwd) 

65 if IS_WINDOWS: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true1ac

66 startupinfo = subprocess.STARTUPINFO() 

67 try: 

68 import _subprocess 

69 startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW 

70 except ImportError: 

71 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 

72 subprocess_args['startupinfo'] = startupinfo 

73 subprocess_args['env'] = dict(os.environ, PYTHONIOENCODING='UTF-8') 

74 else: 

75 subprocess_args['preexec_fn'] = os.setsid 1ac

76 subprocess_args['shell'] = True 1ac

77 self._process = subprocess.Popen(command, **subprocess_args) 1ac

78 self._process.stdin.close() 1a

79 self._output_stream = StreamReaderThread(self._process.stdout) 1a

80 self._error_stream = StreamReaderThread(self._process.stderr) 1a

81 self._output_stream.run() 1a

82 self._error_stream.run() 1a

83 self._kill_called = False 1a

84 

85 def set_port(self, port): 

86 self._port = port 

87 

88 def get_output(self): 

89 return self._output_stream.pop() 1a

90 

91 def get_errors(self): 

92 return self._error_stream.pop() 1a

93 

94 def get_returncode(self): 

95 return self._process.returncode 

96 

97 def is_alive(self): 

98 return self._process.poll() is None 1a

99 

100 def wait(self): 

101 self._process.wait() 

102 

103 def kill(self, force=False, killer_pid=None): 

104 if not self._process: 

105 return 

106 if force: 

107 self._process.kill() 

108 self.resume() # Send so that RF is not blocked 

109 if IS_WINDOWS and not self._kill_called and self._port is not None: 

110 self._signal_kill_with_listener_server() 

111 self._kill_called = True 

112 else: 

113 self._kill(killer_pid or self._process.pid) 

114 

115 def _signal_kill_with_listener_server(self): 

116 self._send_socket('kill') 

117 

118 def _send_socket(self, data): 

119 if self._port is None: 

120 return # Silent failure.. 

121 sock = None 

122 if IS_WINDOWS: 

123 host = '127.0.0.1' 

124 else: 

125 host = 'localhost' 

126 try: 

127 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

128 sock.connect((host, self._port)) 

129 sock.send(bytes(data, OUTPUT_ENCODING)) 

130 # except Exception: 

131 # print(r"DEBUG: Exception at send socket %s" % data) 

132 finally: 

133 sock.close() 

134 

135 def pause(self): 

136 self._send_socket('pause') 

137 

138 def pause_on_failure(self, pause): 

139 if pause: 

140 self._send_socket('pause_on_failure') 

141 else: 

142 self._send_socket('do_not_pause_on_failure') 

143 

144 def resume(self): 

145 self._send_socket('resume') 

146 

147 def step_next(self): 

148 self._send_socket('step_next') 

149 

150 def step_over(self): 

151 self._send_socket('step_over') 

152 

153 @staticmethod 

154 def _kill(pid): 

155 if pid: 

156 try: 

157 os.kill(pid, signal.SIGINT) 

158 except OSError: 

159 pass 

160 

161 

162class StreamReaderThread(object): 

163 

164 def __init__(self, stream): 

165 self._queue = Queue() 1a

166 self._thread = None 1a

167 self._stream = stream 1a

168 

169 def run(self): 

170 self._thread = threading.Thread(target=self._enqueue_output, 1a

171 args=(self._stream,)) 

172 self._thread.daemon = True 1a

173 self._thread.start() 1a

174 

175 def _enqueue_output(self, out): 

176 for line in iter(out.readline, b''): 1a

177 self._queue.put(line) 1a

178 

179 def pop(self): 

180 result = b'' 1a

181 my_queue_rng = range(self._queue.qsize()) 1a

182 for _ in my_queue_rng: 1a

183 try: 1a

184 result += self._queue.get_nowait() 1a

185 except Empty: 

186 pass 

187 return result 1a