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
« 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.
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.
32import os
33import signal
34import socket
35import subprocess
36import sys
37import threading
39from queue import Empty, Queue
40from robotide.context import IS_WINDOWS
42OUTPUT_ENCODING = sys.getfilesystemencoding()
45class Process(object):
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
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
85 def set_port(self, port):
86 self._port = port
88 def get_output(self):
89 return self._output_stream.pop() 1a
91 def get_errors(self):
92 return self._error_stream.pop() 1a
94 def get_returncode(self):
95 return self._process.returncode
97 def is_alive(self):
98 return self._process.poll() is None 1a
100 def wait(self):
101 self._process.wait()
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)
115 def _signal_kill_with_listener_server(self):
116 self._send_socket('kill')
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()
135 def pause(self):
136 self._send_socket('pause')
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')
144 def resume(self):
145 self._send_socket('resume')
147 def step_next(self):
148 self._send_socket('step_next')
150 def step_over(self):
151 self._send_socket('step_over')
153 @staticmethod
154 def _kill(pid):
155 if pid:
156 try:
157 os.kill(pid, signal.SIGINT)
158 except OSError:
159 pass
162class StreamReaderThread(object):
164 def __init__(self, stream):
165 self._queue = Queue() 1a
166 self._thread = None 1a
167 self._stream = stream 1a
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
175 def _enqueue_output(self, out):
176 for line in iter(out.readline, b''): 1a
177 self._queue.put(line) 1a
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