Coverage for src/robotide/contrib/testrunner/testrunner.py: 37%
124 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 socketserver as SocketServer
33import threading
35from robotide.contrib.testrunner.Process import Process
36from robotide.contrib.testrunner.TestRunnerAgent import StreamHandler
37from robotide.controller.testexecutionresults import TestExecutionResults
40# Solution from https://stackoverflow.com/questions/10009753/
41# python-dealing-with-mixed-encoding-files
42def mixed_decoder(unicode_error):
43 err_str = unicode_error[1]
44 err_len = unicode_error.end - unicode_error.start
45 next_position = unicode_error.start + err_len
46 err_hex = err_str[unicode_error.start:unicode_error.end].encode('hex')
47 # Alternative, return u'?', next_position
48 return u'%s' % err_hex, next_position # Comment this line out to get a ?
50# codecs.register_error("mixed", mixed_decoder)
53class TestRunner(object):
55 def __init__(self, project):
56 self._process = None
57 self._server = None
58 self._server_thread = None
59 self._pause_on_failure = False
60 self._pid_to_kill = None
61 self._results = TestExecutionResults()
62 self._port = None
63 self._project = project
64 self.profiles = {}
65 self._pause_longname = None
66 self._pause_testname = None
68 def enable(self, result_handler):
69 self._start_listener_server(result_handler)
71 def add_profile(self, name, item):
72 self.profiles[name] = item
74 def get_profile(self, name):
75 return self.profiles[name]
77 def get_profile_names(self):
78 return sorted(self.profiles.keys())
80 def _start_listener_server(self, result_handler):
81 def handle(*args):
82 self._result_handler(*args)
83 result_handler(*args)
85 self._server = RideListenerServer(RideListenerHandler, handle)
86 self._server_thread = threading.Thread(
87 target=self._server.serve_forever)
88 # DEPRECATED: self._server_thread.setDaemon(True)
89 self._server_thread.daemon = True
90 self._server_thread.start()
91 self._port = self._server.server_address[1]
93 def _result_handler(self, event, *args):
94 if event == 'pid':
95 self._pid_to_kill = int(args[0])
96 if event == 'port' and self._process:
97 self._process.set_port(args[0])
98 if event == 'start_test':
99 longname = args[1]['longname']
100 testname = args[0]
101 self._results.set_running(self._get_test_controller(longname,
102 testname))
103 self._pause_longname = longname
104 self._pause_testname = testname
106 if event == 'continue':
107 self._results.set_running(self._get_test_controller(
108 self._pause_longname, self._pause_testname))
110 if event == 'paused':
111 self._results.set_paused(self._get_test_controller(
112 self._pause_longname, self._pause_testname))
113 if event == 'end_test':
114 longname = args[1]['longname']
115 testname = args[0]
116 if args[1]['status'] == 'PASS':
117 self._results.set_passed(self._get_test_controller(longname,
118 testname))
119 elif args[1]['status'] == 'SKIP':
120 self._results.set_skipped(self._get_test_controller(longname,
121 testname))
122 else:
123 self._results.set_failed(self._get_test_controller(longname,
124 testname))
126 def _get_test_controller(self, longname, testname=None):
127 ret = self._project.find_controller_by_longname(longname, testname)
128 return ret
130 def clear_server(self):
131 self._server = None
133 def shutdown_server(self):
134 if self._server:
135 self._server.shutdown()
137 def test_execution_started(self):
138 self._results.test_execution_started()
140 def kill_process(self):
141 if self._process:
142 self._process.kill(force=True)
144 def send_pause_on_failure(self, pause):
145 if self._process:
146 self._process.pause_on_failure(pause)
148 def send_stop_signal(self):
149 if self._process:
150 self._process.kill(killer_pid=self._pid_to_kill)
152 def send_pause_signal(self):
153 if self._process:
154 self._process.pause()
156 def send_continue_signal(self):
157 if self._process:
158 self._process.resume()
160 def send_step_next_signal(self):
161 if self._process:
162 self._process.step_next()
164 def send_step_over_signal(self):
165 if self._process:
166 self._process.step_over()
168 def run_command(self, command, cwd):
169 self._pid_to_kill = None
170 self._process = Process(cwd)
171 self._process.run_command(command)
173 def get_output_and_errors(self, profile):
174 stdout, stderr, returncode = self._process.get_output(), \
175 self._process.get_errors(), \
176 self._process.get_returncode()
177 error, log_message = profile.format_error(stderr, returncode)
178 return stdout, error, log_message
180 def get_listener_port(self):
181 return self._port
183 def is_running(self):
184 return self._process and self._process.is_alive()
186 def command_ended(self):
187 self._results.set_stopped(None)
188 self._process = None
191# The following two classes implement a small line-buffered socket
192# server. It is designed to run in a separate thread, read data
193# from the given port and update the UI -- hopefully all in a
194# thread-safe manner.
195class RideListenerServer(SocketServer.TCPServer):
196 """Implements a simple line-buffered socket server"""
197 allow_reuse_address = True
199 def __init__(self, request_handler_class, callback):
200 SocketServer.TCPServer.__init__(self, ("", 0), request_handler_class)
201 self.callback = callback
204class RideListenerHandler(SocketServer.StreamRequestHandler):
205 def handle(self):
206 decoder = StreamHandler(self.request.makefile('r'))
207 while True:
208 try:
209 (name, args) = decoder.load()
210 self.server.callback(name, *args)
211 except (EOFError, IOError):
212 # I should log this...
213 break