Robot Framework Integrated Development Environment (RIDE)
testrunnerplugin.py
Go to the documentation of this file.
1 # -*- encoding: utf-8 -*-
2 # Copyright 2010 Orbitz WorldWide
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 # Modified by NSN
17 # Copyright 2010-2012 Nokia Solutions and Networks
18 # Copyright 2013-2015 Nokia Networks
19 # Copyright 2016- Robot Framework Foundation
20 #
21 # Licensed under the Apache License, Version 2.0 (the "License");
22 # you may not use this file except in compliance with the License.
23 # You may obtain a copy of the License at
24 #
25 # http://www.apache.org/licenses/LICENSE-2.0
26 #
27 # Unless required by applicable law or agreed to in writing, software
28 # distributed under the License is distributed on an "AS IS" BASIS,
29 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 # See the License for the specific language governing permissions and
31 # limitations under the License.
32 
33 
48 import atexit
49 import datetime
50 import shutil
51 import subprocess
52 import tempfile
53 import threading
54 import time
55 import os
56 import psutil
57 import re
58 import wx
59 import wx.stc
60 from functools import reduce
61 from queue import Queue
62 from wx.lib.embeddedimage import PyEmbeddedImage
63 
64 from robotide.action.shortcut import localize_shortcuts
65 from robotide.context import IS_WINDOWS, IS_MAC
66 from robotide.contrib.testrunner import TestRunner
67 from robotide.contrib.testrunner import runprofiles
68 from robotide.contrib.testrunner.ArgsParser import ArgsParser
69 from robotide.contrib.testrunner.CommandArgs import CommandArgs
70 from robotide.contrib.testrunner.Command import Command
71 from robotide.contrib.testrunner.FileWriter import FileWriter
72 from robotide.contrib.testrunner.SettingsParser import SettingsParser
73 from robotide.controller.macrocontrollers import TestCaseController
74 from robotide.publish import RideSettingsChanged, PUBLISHER
75 from robotide.publish.messages import RideTestSelectedForRunningChanged
76 from robotide.pluginapi import Plugin, ActionInfo
77 from robotide.ui.notebook import NoteBook
78 from robotide.widgets import Label, ImageProvider, RIDEDialog
79 from robotide.robotapi import LOG_LEVELS
80 from robotide.utils import robottime
81 from robotide.preferences.editors import ReadFonts
82 from sys import getfilesystemencoding, platform
83 from robotide.lib.robot.utils.encodingsniffer import (get_console_encoding,
84  get_system_encoding)
85 
86 CONSOLE_ENCODING = get_console_encoding()
87 SYSTEM_ENCODING = get_system_encoding()
88 OUTPUT_ENCODING = getfilesystemencoding()
89 encoding = {'CONSOLE': CONSOLE_ENCODING,
90  'SYSTEM': SYSTEM_ENCODING,
91  'OUTPUT': OUTPUT_ENCODING}
92 
93 ID_RUN = wx.NewIdRef()
94 ID_RUNDEBUG = wx.NewIdRef()
95 ID_STOP = wx.NewIdRef()
96 ID_PAUSE = wx.NewIdRef()
97 ID_CONTINUE = wx.NewIdRef()
98 ID_STEP_NEXT = wx.NewIdRef()
99 ID_STEP_OVER = wx.NewIdRef()
100 ID_OPEN_LOGS_DIR = wx.NewId()
101 ID_SHOW_REPORT = wx.NewIdRef()
102 ID_SHOW_LOG = wx.NewIdRef()
103 ID_AUTOSAVE = wx.NewIdRef()
104 ID_PAUSE_ON_FAILURE = wx.NewIdRef()
105 ID_SHOW_MESSAGE_LOG = wx.NewIdRef()
106 STYLE_DEFAULT = 0
107 STYLE_STDERR = 2
108 STYLE_PASS = 1
109 STYLE_SKIP = 3
110 STYLE_FAIL = 4
111 
112 ATEXIT_LOCK = threading.RLock()
113 
114 
115 def _RunProfile(name, run_prefix):
116  return type('Profile', (runprofiles.PybotProfile,),
117  {'name': name, 'get_command': lambda self: run_prefix})
118 
119 
120 def open_filemanager(path=None):
121  path = path or os.path.curdir
122  path_dir = os.path.dirname(path) if os.path.isfile(path) else path
123  if os.path.exists(path_dir):
124  if platform == 'win32':
125  # There was encoding errors if directory had unicode chars
126  # TODO test on all OS directory names with accented chars, for example 'ccedilla'
127  os.startfile(r"%s" % path_dir, 'explore')
128  elif platform.startswith('linux'):
129  # how to detect which explorer is used?
130  # nautilus, dolphin, konqueror
131  # TODO check if explorer exists
132  # TODO get prefered explorer from preferences
133  try:
134  subprocess.Popen(["nautilus", "{}".format(path_dir)])
135  except OSError or FileNotFoundError:
136  try:
137  subprocess.Popen(
138  ["dolphin", "{}".format(path_dir)])
139  except OSError or FileNotFoundError:
140  try:
141  subprocess.Popen(
142  ["konqueror", "{}".format(path_dir)])
143  except OSError or FileNotFoundError:
144  print("Could not launch explorer. Tried nautilus, "
145  "dolphin and konqueror.")
146  else:
147  try:
148  subprocess.Popen(["finder", "{}".format(path_dir)])
149  except OSError or FileNotFoundError:
150  subprocess.Popen(["open", "{}".format(path_dir)])
151 
152 
153 
154 class TestRunnerPlugin(Plugin):
155  defaults = {"auto_save": False,
156  "confirm run": True,
157  "profile_name": "robot",
158  "show_console_log": True,
159  "show_message_log": True,
160  "sash_position": 200,
161  "run_profiles":
162  [('jybot', 'jybot' + ('.bat' if os.name == 'nt' else '')),
163  ('pybot', 'pybot' + ('.bat' if os.name == 'nt' else '')),
164  ('robot 3.1', 'robot')],
165  "font size": 10,
166  "font face": 'Courier New',
167  "foreground": 'black',
168  "background": 'white',
169  "error": 'red',
170  "use colors": False,
171  "fail color": '#FF8E8E',
172  "pass color": '#9FCC9F',
173  "skip color": 'yellow'
174  }
175 
176  report_regex = re.compile("^Report: {2}(.*\.html)$", re.MULTILINE)
177  log_regex = re.compile("^Log: {5}(.*\.html)$", re.MULTILINE)
178  title = "Run"
179 
180  def __init__(self, application=None):
181  Plugin.__init__(self, application, initially_enabled=True,
182  default_settings=self.defaultsdefaults)
183  self.versionversion = "3.1"
184  self.metadatametadata = {
185  "url":
186  "https://github.com/robotframework/RIDE/wiki/Test-Runner-Plugin"}
187  self._reload_timer_reload_timer = None
188  self._frame_frame = application.frame
189  self._report_file_report_file = None
190  self._log_file_log_file = None
191  self._controls_controls = {}
192  self._running_running = False
193  self._currently_executing_keyword_currently_executing_keyword = None
194  self._test_runner_test_runner = TestRunner(application.model)
195  self._register_shortcuts_register_shortcuts()
196  self._min_log_level_number_min_log_level_number = LOG_LEVELS['INFO']
197  self._pause_on_failure_pause_on_failure = False
198  self._selected_tests_selected_tests: {TestCaseController} = set()
199  self._process_process = psutil.Process()
200  self._initmemory_initmemory = None
201  self._limitmemory_limitmemory = None # This will be +80%
202  self._maxmemmsg_maxmemmsg = None
203  self.use_colorsuse_colors = self.__getattr__('use colors')
204  self.fail_colorfail_color = self.__getattr__('fail color')
205  self.pass_colorpass_color = self.__getattr__('pass color')
206  self.skip_colorskip_color = self.__getattr__('skip color')
207 
208  @property
209  _names_to_run = property
210 
211  def _names_to_run(self):
212  return list(
213  map(lambda ctrl: (ctrl.datafile_controller.longname, ctrl.longname),
214  self._selected_tests_selected_tests))
215 
217  self.register_shortcut('CtrlCmd-C', self._copy_from_log_ctrls_copy_from_log_ctrls)
218  self.register_shortcut('CtrlCmd-L', self.OnShowLogOnShowLog)
219  self.register_shortcut('CtrlCmd-R', self.OnShowReportOnShowReport)
220  if IS_WINDOWS or IS_MAC:
221  self.register_shortcut('Del', self._delete_pressed_delete_pressed)
222 
223  def _delete_pressed(self, event):
224  if self.notebook.current_page_title != self.titletitle:
225  return
226  self.get_current_profileget_current_profile().delete_pressed()
227 
228  def _copy_from_log_ctrls(self, event):
229  if self.notebook.current_page_title != self.titletitle:
230  return
231  if self._console_log_ctrl_console_log_ctrl.GetSTCFocus():
232  self._console_log_ctrl_console_log_ctrl.Copy()
233  return
234  if self._message_log_ctrl_message_log_ctrl.GetSTCFocus():
235  self._message_log_ctrl_message_log_ctrl.Copy()
236 
237  def enable(self):
238  self.tree.set_checkboxes_for_tests()
239  self._read_run_profiles_read_run_profiles()
240  self._register_actions_register_actions()
241  self._add_tab_to_notebook_add_tab_to_notebook()
242  self._init_profile_choice_init_profile_choice(self.profile_name)
243  self._subscribe_to_events_subscribe_to_events()
244  self._test_runner_test_runner.enable(self._test_runner_events_handler_test_runner_events_handler)
245  self._set_stopped_set_stopped()
246  self._create_temporary_directory_create_temporary_directory()
247 
248  def _register_actions(self):
249  run_action_info = ActionInfo("Tools", "Run Tests", self.OnRunOnRun, None,
250  "F8", ImageProvider().TOOLBAR_PLAY,
251  "Run the selected tests", position=10)
252  self._run_action_run_action = self.register_action(run_action_info)
253  run_action_debug = ActionInfo("Tools", "Run Tests with Debug",
254  self.OnRunDebugOnRunDebug, None,
255  "F9", getBugIconBitmap(),
256  "Run the selected tests with Debug",
257  position=8)
258  self._run_action_run_action = self.register_action(run_action_debug)
259  stop_action_info = ActionInfo("Tools", "Stop Test Run", self.OnStopOnStop,
260  None, "CtrlCmd-F8",
261  ImageProvider().TOOLBAR_STOP,
262  "Stop a running test", position=11)
263  self._stop_action_stop_action = self.register_action(stop_action_info)
264 
266  self._read_run_profiles_from_config_read_run_profiles_from_config()
267  self._read_run_profiles_from_classes_read_run_profiles_from_classes()
268 
270  # Have to keep reference so that these classes are not garbage collected
271  self._profile_classes_from_config_profile_classes_from_config = [_RunProfile(name, run_prefix)
272  for name, run_prefix in
273  self.run_profiles]
274 
276  for profile in self._get_all_subclasses_get_all_subclasses(runprofiles.BaseProfile):
277  self._test_runner_test_runner.add_profile(profile.name, profile(plugin=self))
278 
279  def _get_all_subclasses(self, class_):
280  classes = []
281  for sub_class in class_.__subclasses__():
282  classes += [sub_class] + self._get_all_subclasses_get_all_subclasses(sub_class)
283  return classes
284 
286  self.subscribe(self.OnTestSelectedForRunningChangedOnTestSelectedForRunningChanged,
287  RideTestSelectedForRunningChanged)
288  self.subscribe(self.OnSettingsChangedOnSettingsChanged, RideSettingsChanged)
289 
290 
291  def OnSettingsChanged(self, message):
292  section, setting = message.keys
293  # print("DEBUG: enter OnSettingsChanged section %s" % (section))
294  if section == 'Test Run': # DEBUG temporarily we have two sections
295  # print("DEBUG: setting.get('confirm run')= %s " % setting)
296  # print("DEBUG: new data= %s old %s new %s" % (data.keys, data.old, data.new))
297  self.defaultsdefaults.setdefault(setting, message.new)
298  self.save_setting(setting, message.new)
299 
301  self._selected_tests_selected_tests = message.tests
302 
303  def disable(self):
304  self._remove_from_notebook_remove_from_notebook()
305  self._test_runner_test_runner.clear_server()
306  self.unsubscribe_all()
307  self.unregister_actions()
308 
310  self._default_output_dir_default_output_dir = tempfile.mkdtemp(".d", "RIDE")
311  atexit.register(self._remove_temporary_directory_remove_temporary_directory)
312  # this plugin creates a temporary directory which _should_
313  # get reaped at exit. Sometimes things happen which might
314  # cause it to not get deleted. Maybe this would be a good
315  # place to check for temporary directories that match the
316  # signature and delete them if they are more than a few
317  # days old...
318 
320  with ATEXIT_LOCK:
321  if os.path.exists(self._default_output_dir_default_output_dir):
322  shutil.rmtree(self._default_output_dir_default_output_dir)
323 
324 
325  def OnClose(self, event):
326  self._test_runner_test_runner.kill_process()
327  if self._process_timer_process_timer:
328  self._process_timer_process_timer.Stop()
329  self._test_runner_test_runner.shutdown_server()
330  event.Skip()
331 
333  self._initmemory_initmemory = self._process_process.memory_info()[0]
334  self._limitmemory_limitmemory = self._initmemory_initmemory * 1.80
335  self._maxmemmsg_maxmemmsg = None
336 
337 
342  def OnStop(self, event):
343  self._reset_memory_calc_reset_memory_calc()
344  self._append_to_console_log_append_to_console_log('[ SENDING STOP SIGNAL ]\n',
345  source='stderr')
346  self._test_runner_test_runner.send_stop_signal()
347 
348  def OnPause(self, event):
349  self._reset_memory_calc_reset_memory_calc()
350  self._append_to_console_log_append_to_console_log('[ SENDING PAUSE SIGNAL ]\n')
351  self._test_runner_test_runner.send_pause_signal()
352 
353  def OnContinue(self, event):
354  self._reset_memory_calc_reset_memory_calc()
355  self._append_to_console_log_append_to_console_log('[ SENDING CONTINUE SIGNAL ]\n')
356  self._test_runner_test_runner.send_continue_signal()
357 
358  def OnStepNext(self, event):
359  self._reset_memory_calc_reset_memory_calc()
360  self._append_to_console_log_append_to_console_log('[ SENDING STEP NEXT SIGNAL ]\n')
361  self._test_runner_test_runner.send_step_next_signal()
362 
363  def OnStepOver(self, event):
364  self._reset_memory_calc_reset_memory_calc()
365  self._append_to_console_log_append_to_console_log('[ SENDING STEP OVER SIGNAL ]\n')
366  self._test_runner_test_runner.send_step_over_signal()
367 
368 
369  def OnRun(self, event):
370  self._run_tests_run_tests()
371 
372 
375  def OnRunDebug(self, event):
376  self._run_tests_run_tests("DEBUG")
377 
378  def _run_tests(self, log_level='INFO'):
379  if not self._can_start_running_tests_can_start_running_tests():
380  return
381  if self.__getattr__('confirm run') \
382  and not self._tests_selected_tests_selected() \
383  and not self._ask_user_to_run_anyway_ask_user_to_run_anyway():
384  # In Linux NO runs dialog 4 times
385  return
386  self._reset_memory_calc_reset_memory_calc()
387  profile = self.get_current_profileget_current_profile()
388  self.use_colorsuse_colors = self.__getattr__('use colors')
389  command_args = self._create_command_args_create_command_args(profile.get_command_args(), log_level, self.use_colorsuse_colors)
390  # wx.MessageBox(f"DEBUG: after _create_command_args {command_args}", "Debug")
391  # print(f"DEBUG: testrunnerplugin _run_tests BEFORE _save_command_args_in_file")
392  args_file = self._save_command_args_in_file_save_command_args_in_file(command_args)
393  # print(f"DEBUG: testrunnerplugin _run_tests AFTER _save_command_args_in_file")
394  command = self._create_command_create_command(profile.get_command(), args_file)
395  self._initialize_variables_for_running_initialize_variables_for_running(profile.get_settings(), command_args)
396  self._initialize_ui_for_running_initialize_ui_for_running()
397  # DEBUG on Py3 it not shows correct if tags with latin chars
398  self._append_to_console_log_append_to_console_log("command: %s\n" % command)
399  try:
400  self._test_runner_test_runner.run_command(command, self._get_current_working_dir_get_current_working_dir(profile))
401  self._process_timer_process_timer.Start(41) # roughly 24fps
402  self._set_running_set_running()
403  self._progress_bar_progress_bar.Start()
404  except Exception as e:
405  self._set_stopped_set_stopped()
406  error, log_message = self.get_current_profileget_current_profile().format_error(str(e), None)
407  self._append_to_console_log_append_to_console_log(error, source='stderr')
408  if log_message:
409  log_message.publish()
410 
411  def _create_command_args(self, profile_command_args, log_level='INFO', use_colors=False):
412  return CommandArgs().with_existing_args(profile_command_args) \
413  .with_log_level(log_level) \
414  .with_output_directory(self._default_output_dir_default_output_dir) \
415  .with_python_path(self.global_settings.get('pythonpath', None)) \
416  .with_console_width(self._get_console_width_get_console_width()) \
417  .without_console_color(not use_colors) \
418  .with_runnable_tests(self._names_to_run_names_to_run_names_to_run) \
419  .build()
420 
421  def _save_command_args_in_file(self, args):
422  arg_file = os.path.join(self._default_output_dir_default_output_dir, 'argfile.txt')
423  FileWriter.write(arg_file, args, 'wb')
424  return arg_file
425 
426  def _create_command(self, profile_command, args_file):
427  return Command().with_prefix(profile_command) \
428  .with_args_file(args_file) \
429  .with_listener(self._test_runner_test_runner.get_listener_port(),
430  self._pause_on_failure_pause_on_failure) \
431  .with_tests_suite_file(self.model.suite.source) \
432  .build()
433 
434  def _initialize_variables_for_running(self, profile_settings, args):
435  self._report_file_report_file = self._log_file_log_file = None
436  self._log_message_queue_log_message_queue = Queue()
437 
438  self._min_log_level_number_min_log_level_number = \
439  ArgsParser.get_message_log_level(args)
440 
441  self._logs_directory_logs_directory = \
442  ArgsParser.get_output_directory(args, self._default_output_dir_default_output_dir)
443 
444  console_log_name = \
445  SettingsParser.get_console_log_name(profile_settings)
446  self._console_log_console_log = '' if not console_log_name \
447  else os.path.join(self._logs_directory_logs_directory, console_log_name)
448 
449  def _get_current_working_dir(self, profile):
450  if profile.name == runprofiles.CustomScriptProfile.name:
451  return profile.get_cwd()
452  if os.path.isdir(self.model.suite.source):
453  return self.model.suite.source
454  return os.path.dirname(self.model.suite.source)
455 
457  if self._running_running or self.model.suite is None:
458  return False
459  if not self.is_unsaved_changes():
460  return True
461  if self.auto_save or self._ask_user_to_save_before_running_ask_user_to_save_before_running():
462  self.save_all_unsaved_changes()
463  return True
464  return False
465 
466  @staticmethod
468  ret = wx.MessageBox("""There are unsaved modifications.
469  Do you want to save all changes and run the tests?""",
470  "Unsaved Modifications",
471  wx.ICON_QUESTION | wx.YES_NO)
472  return ret == wx.YES
473 
474  def _tests_selected(self):
475  return len(self._selected_tests_selected_tests) != 0
476 
477  @staticmethod
479  ret = wx.MessageBox('No tests selected. \n'
480  'Continue anyway?',
481  'No tests selected',
482  wx.ICON_QUESTION | wx.YES_NO)
483  return ret == wx.YES
484 
486  self._show_notebook_tab_show_notebook_tab()
487  self._clear_log_ctrls_clear_log_ctrls()
488  self._local_toolbar_local_toolbar.EnableTool(ID_OPEN_LOGS_DIR, False)
489  self._local_toolbar_local_toolbar.EnableTool(ID_SHOW_REPORT, False)
490  self._local_toolbar_local_toolbar.EnableTool(ID_SHOW_LOG, False)
491  self._report_file_report_file = self._log_file_log_file = None
492  self._log_message_queue_log_message_queue = Queue()
493 
494  def _clear_log_ctrls(self):
495  self._clear_text_ctrl_clear_text_ctrl(self._console_log_ctrl_console_log_ctrl)
496  self._clear_text_ctrl_clear_text_ctrl(self._message_log_ctrl_message_log_ctrl)
497 
498  @staticmethod
499  def _clear_text_ctrl(text_ctrl):
500  text_ctrl.SetReadOnly(False)
501  text_ctrl.ClearAll()
502  text_ctrl.SetReadOnly(True)
503 
504 
505  def OnOpenLogsDirectory(self, evt):
506  if os.path.exists(self._logs_directory_logs_directory):
507  open_filemanager(self._logs_directory_logs_directory)
508  else:
509  self._notify_user_no_logs_directory_notify_user_no_logs_directory()
510 
511 
512  def OnShowReport(self, evt):
513  if self._report_file_report_file:
514  wx.LaunchDefaultBrowser(
515  "file:%s" % os.path.abspath(self._report_file_report_file))
516 
517 
518  def OnShowLog(self, evt):
519  if self._log_file_log_file:
520  wx.LaunchDefaultBrowser("file:%s" % os.path.abspath(self._log_file_log_file))
521 
522  def OnProcessEnded(self, evt):
523  output, errors, log_message = self._test_runner_test_runner.get_output_and_errors(
524  self.get_current_profileget_current_profile())
525  self._append_to_console_log_append_to_console_log(output)
526  self._read_report_and_log_from_stdout_if_needed_read_report_and_log_from_stdout_if_needed()
527  if len(errors) > 0:
528  self._append_to_console_log_append_to_console_log(errors, source="stderr")
529  if self._process_timer_process_timer:
530  self._process_timer_process_timer.Stop()
531  self._set_stopped_set_stopped()
532  self._progress_bar_progress_bar.Stop()
533  now = datetime.datetime.now().timetuple()
534  self._append_to_console_log_append_to_console_log("\nTest finished {}"
535  .format(robottime.format_time(now)))
536  self._test_runner_test_runner.command_ended()
537  if log_message:
538  log_message.publish()
539  self._local_toolbar_local_toolbar.EnableTool(ID_OPEN_LOGS_DIR, True)
540 
542  output = self._console_log_ctrl_console_log_ctrl.GetText()
543  if not self._report_file_report_file:
544  self._report_file_report_file = \
545  self._get_report_or_log_get_report_or_log(output, self.report_regexreport_regex)
546  if self._report_file_report_file:
547  self._local_toolbar_local_toolbar.EnableTool(ID_SHOW_REPORT, True)
548  if not self._log_file_log_file:
549  self._log_file_log_file = self._get_report_or_log_get_report_or_log(output, self.log_regexlog_regex)
550  if self._log_file_log_file:
551  self._local_toolbar_local_toolbar.EnableTool(ID_SHOW_LOG, True)
552 
553  @staticmethod
554  def _get_report_or_log(output, regex):
555  res = regex.search(output)
556  return res.group(1) if res and os.path.isfile(res.group(1)) else None
557 
558 
559  def OnTimer(self, evt):
560  if not self._log_message_queue_log_message_queue.empty():
561  if self._process_process.memory_info()[0] <= self._limitmemory_limitmemory:
562  texts = []
563  while not self._log_message_queue_log_message_queue.empty():
564  texts += [self._log_message_queue_log_message_queue.get()]
565  self._append_to_message_log_append_to_message_log('\n' + '\n'.join(texts))
566  else:
567  if not self._maxmemmsg_maxmemmsg:
568  self._maxmemmsg_maxmemmsg = '\n' + "Messages log exceeded 80% of " \
569  "process memory, stopping for now..."
570  self._append_to_message_log_append_to_message_log(self._maxmemmsg_maxmemmsg, "stderr")
571  if not self._test_runner_test_runner.is_running():
572  self.OnProcessEndedOnProcessEnded(None)
573  return
574  out_buffer, err_buffer, log_message = \
575  self._test_runner_test_runner.get_output_and_errors(self.get_current_profileget_current_profile())
576  if len(out_buffer) > 0:
577  self._append_to_console_log_append_to_console_log(out_buffer, source="stdout")
578  if len(err_buffer) > 0:
579  if self._get_last_output_char_get_last_output_char() != "\n":
580  # Robot prints partial lines to stdout to make the
581  # interactive experience better. It all goes to
582  # heck in a handbasket if something shows up on
583  # stderr. So, to fix that we'll add a newline if
584  # the previous character isn't a newline.
585  self._append_to_console_log_append_to_console_log("\n")
586  self._append_to_console_log_append_to_console_log(err_buffer, source="stderr")
587 
588 
592  pos = self._console_log_ctrl_console_log_ctrl.PositionBefore(
593  self._console_log_ctrl_console_log_ctrl.GetLength())
594  char = self._console_log_ctrl_console_log_ctrl.GetCharAt(pos)
595  return chr(char)
596 
597 
601  if not self.panelpanel:
602  self._add_tab_to_notebook_add_tab_to_notebook()
603  self._reload_model()
604  self.show_tab(self.panelpanel)
605 
606  def _append_to_message_log(self, text, source="stdout"):
607  self._append_text_append_text(self._message_log_ctrl_message_log_ctrl, text, source)
608 
609 
612  def _append_to_console_log(self, text, source="stdout"):
613  self._append_text_append_text(self._console_log_ctrl_console_log_ctrl, text, source)
614  if self._console_log_console_log:
615  FileWriter.write(self._console_log_console_log, [text], "ab", "a")
616 
617  def _append_text(self, text_ctrl, text, source="stdout"):
618  # text could be bytes or str
619  if not self.panelpanel or not text_ctrl:
620  return
621  self._color_map_color_map = list()
622  if self.use_colorsuse_colors:
623  text = self.parse_colorsparse_colors(text)
624  text_ctrl.update_scroll_width(text)
625  # we need this information to decide whether to autoscroll or not
626  new_text_start = text_ctrl.GetLength()
627  line_count = text_ctrl.GetLineCount()
628  last_visible_line = \
629  text_ctrl.GetFirstVisibleLine() + text_ctrl.LinesOnScreen() - 1
630 
631  text_ctrl.SetReadOnly(False)
632  pos=text_ctrl.GetLastPosition() # TODO: Process \r with Replace
633  # print(f"DEBUG: _append_text lastposition={pos}")
634  text_ctrl.AppendText(text)
635  new_text_end = text_ctrl.GetLength()
636 
637  if wx.VERSION < (4, 1, 0):
638  text_ctrl.StartStyling(new_text_start, 0x1f)
639  else:
640  text_ctrl.StartStyling(new_text_start)
641  text_ctrl.SetStyling(0, STYLE_DEFAULT)
642  if source == "stderr" and not self.use_colorsuse_colors:
643  text_ctrl.SetStyling(new_text_end - new_text_start, STYLE_STDERR)
644 
645  if self.use_colorsuse_colors and self._color_map_color_map:
646  style = None
647  previous_start = None
648  for item in self._color_map_color_map:
649  previous_style = style
650  if item[1] == 'RED':
651  style = STYLE_FAIL
652  elif item[1] == 'GREEN':
653  style = STYLE_PASS
654  elif item[1] == 'YELLOW':
655  style = STYLE_SKIP
656  elif item[1] is None:
657  style = STYLE_DEFAULT
658  if style:
659  if wx.VERSION < (4, 1, 0):
660  text_ctrl.StartStyling(pos + item[0], 0x1f)
661  else:
662  text_ctrl.StartStyling(pos + item[0])
663  previous_start = item[0]
664  if previous_style and style == STYLE_DEFAULT:
665  text_ctrl.SetStyling(item[0] - previous_start, previous_style)
666  if previous_start and style:
667  text_ctrl.SetStyling(item[0] - previous_start, STYLE_DEFAULT)
668 
669  text_ctrl.SetReadOnly(True)
670  if last_visible_line >= line_count - 4:
671  line_count = text_ctrl.GetLineCount()
672  text_ctrl.ScrollToLine(line_count)
673 
674  def parse_colors(self, txt):
675  # print(f"DEBUG: enter parse_colors {txt}")
676  idx = 0
677  t_size = len(txt)
678  while idx < t_size:
679  # print(f"{str(txt[idx])}")
680  if txt[idx] == 27: # .startswith('\033[32m'):
681  color = False
682  # print(f"DEBUG: parse_colors got ESC, {txt[idx+1:idx+5]}")
683  if txt[idx + 1:idx+5] == b'[34m':
684  color = True
685  self.store_colorstore_color(idx, 'BLUE')
686  # print(f"DEBUG: parse_colors got BLUE")
687  if txt[idx + 1:idx+5] == b'[33m':
688  color = True
689  self.store_colorstore_color(idx, 'YELLOW')
690  # print(f"DEBUG: parse_colors got YELLOW")
691  if txt[idx+1:idx+5] == b'[32m':
692  color = True
693  self.store_colorstore_color(idx, 'GREEN')
694  # print(f"DEBUG: parse_colors got GREEN")
695  if txt[idx+1:idx+5] == b'[31m':
696  color = True
697  self.store_colorstore_color(idx, 'RED')
698  # print(f"DEBUG: parse_colors got RED")
699  if txt[idx+1:idx+4] == b'[0m':
700  # print(f"DEBUG: parse_colors reset to NORMAL")
701  self.store_colorstore_color(idx, None)
702  txt = txt[:idx] + txt[idx+4:]
703  # idx += 3
704  elif color:
705  txt = txt[:idx] + txt[idx+5:]
706  # idx += 4
707  if idx >= len(txt):
708  break
709  t_size = len(txt)
710  if idx < t_size:
711  idx += 1
712  return txt
713 
714  def store_color(self, idx, color):
715  if not self._color_map_color_map:
716  self._color_map_color_map = list()
717  self._color_map_color_map.append((idx, color))
718 
720  # robot wants to know a fixed size for output, so calculate the
721  # width of the window based on average width of a character. A
722  # little is subtracted just to make sure there's a little margin
723  out_width, _ = self._console_log_ctrl_console_log_ctrl.GetSize()
724  char_width = self.__getattr__("font size")
725  return str(int(out_width / char_width) - 10)
726 
727 
731  if self.notebook:
732  self.notebook.allow_closing(self.panelpanel)
733  self.notebook.delete_tab(self.panelpanel)
734 
735  def _build_runner_toolbar(self, parent):
736  toolbar = wx.ToolBar(parent, wx.ID_ANY,
737  style=wx.TB_HORIZONTAL | wx.TB_HORZ_TEXT | wx.TB_NODIVIDER)
738  toolbar.SetBackgroundColour(self._mysettings_mysettings.color_background)
739  toolbar.SetForegroundColour(self._mysettings_mysettings.color_foreground)
740  toolbar.AddTool(ID_RUN, "Start", ImageProvider().TOOLBAR_PLAY,
741  wx.NullBitmap, wx.ITEM_NORMAL, shortHelp="Start robot",
742  longHelp="Start running the robot test suite")
743  toolbar.AddTool(ID_RUNDEBUG, "Debug", getBugIconBitmap(), wx.NullBitmap,
744  wx.ITEM_NORMAL, shortHelp="Start robot",
745  longHelp="Start running the robot test suite "
746  "with DEBUG loglevel")
747  toolbar.AddTool(ID_STOP, "Stop", ImageProvider().TOOLBAR_STOP,
748  wx.NullBitmap, wx.ITEM_NORMAL,
749  shortHelp="Stop a running test",
750  longHelp="Stop a running test")
751  toolbar.AddTool(ID_PAUSE, "Pause", ImageProvider().TOOLBAR_PAUSE,
752  wx.NullBitmap, wx.ITEM_NORMAL,
753  shortHelp="Pause test execution",
754  longHelp="Pause test execution")
755  toolbar.AddTool(ID_CONTINUE, "Continue",
756  ImageProvider().TOOLBAR_CONTINUE,
757  wx.NullBitmap, wx.ITEM_NORMAL,
758  shortHelp="Continue test execution",
759  longHelp="Continue test execution")
760  toolbar.AddTool(ID_STEP_NEXT, "Next", ImageProvider().TOOLBAR_NEXT,
761  wx.NullBitmap, wx.ITEM_NORMAL, shortHelp="Step next",
762  longHelp="Step next")
763  toolbar.AddTool(ID_STEP_OVER, "Step over", ImageProvider().TOOLBAR_NEXT,
764  wx.NullBitmap, wx.ITEM_NORMAL, shortHelp="Step over",
765  longHelp="Step over")
766  toolbar.Realize()
767  self._bind_runner_toolbar_events_bind_runner_toolbar_events(toolbar)
768  return toolbar
769 
770  def _bind_runner_toolbar_events(self, toolbar):
771  for event, callback, id in (
772  (wx.EVT_TOOL, self.OnRunOnRun, ID_RUN),
773  (wx.EVT_TOOL, self.OnRunDebugOnRunDebug, ID_RUNDEBUG),
774  (wx.EVT_TOOL, self.OnStopOnStop, ID_STOP),
775  (wx.EVT_TOOL, self.OnPauseOnPause, ID_PAUSE),
776  (wx.EVT_TOOL, self.OnContinueOnContinue, ID_CONTINUE),
777  (wx.EVT_TOOL, self.OnStepNextOnStepNext, ID_STEP_NEXT),
778  (wx.EVT_TOOL, self.OnStepOverOnStepOver, ID_STEP_OVER)):
779  toolbar.Bind(event, callback, id=id)
780 
781  def _build_local_toolbar(self, parent):
782  toolbar = wx.ToolBar(parent, wx.ID_ANY,
783  style=wx.TB_HORIZONTAL | wx.TB_HORZ_TEXT | wx.TB_NODIVIDER | wx.TB_DOCKABLE)
784  # print(f"DEBUG: toolbar before {toolbar.UseBackgroundColour()}")
785  toolbar.SetOwnBackgroundColour(self._mysettings_mysettings.color_background)
786  toolbar.SetOwnForegroundColour(self._mysettings_mysettings.color_foreground)
787  profile_label = Label(toolbar, label="Execution Profile: ")
788  choices = self._test_runner_test_runner.get_profile_names()
789  self.choicechoice = wx.Choice(toolbar, wx.ID_ANY, choices=choices)
790  self.choicechoice.SetToolTip(wx.ToolTip("Choose which method to use for "
791  "running the tests"))
792  toolbar.AddControl(profile_label)
793  toolbar.AddControl(self.choicechoice)
794  toolbar.AddSeparator()
795  report_image = getReportIconBitmap()
796  log_image = getLogIconBitmap()
797  toolbar.AddTool(ID_OPEN_LOGS_DIR, "Open Logs Directory",
798  ImageProvider().DATADIRIMG,
799  shortHelp="View All Logs in Explorer")
800  toolbar.AddTool(ID_SHOW_REPORT, " Report", report_image,
801  shortHelp=localize_shortcuts("View Robot Report in "
802  "Browser (CtrlCmd-R)"))
803  toolbar.AddTool(ID_SHOW_LOG, " Log", log_image,
804  shortHelp=localize_shortcuts("View Robot Log in"
805  " Browser (CtrlCmd-L)"))
806  toolbar.AddSeparator()
807  # the toolbar API doesn't give us a way to specify padding which
808  # is why the label has a couple spaces after the colon. gross,
809  # but effective.
810  self.autosave_cbautosave_cb = \
811  self._create_check_box_create_check_box(toolbar, ID_AUTOSAVE, " Autosave ",
812  self.auto_save, "Automatically save all "
813  "changes before running")
814  toolbar.AddControl(self.autosave_cbautosave_cb)
815 
816  self.pause_on_failure_cbpause_on_failure_cb = \
817  self._create_check_box_create_check_box(toolbar, ID_PAUSE_ON_FAILURE,
818  " Pause after failure ", False,
819  "Automatically pause after failing keyword")
820  toolbar.AddControl(self.pause_on_failure_cbpause_on_failure_cb)
821 
822  toolbar.EnableTool(ID_OPEN_LOGS_DIR, False)
823  toolbar.EnableTool(ID_SHOW_LOG, False)
824  toolbar.EnableTool(ID_SHOW_REPORT, False)
825  for i in toolbar.GetChildren():
826  i.SetBackgroundColour(self._mysettings_mysettings.color_background)
827  i.SetForegroundColour(self._mysettings_mysettings.color_foreground)
828  toolbar.Realize()
829  self._bind_local_toolbar_events_bind_local_toolbar_events(toolbar)
830  # print(f"DEBUG: toolbar end {toolbar.UseBackgroundColour()}")
831  return toolbar
832 
833  def _bind_local_toolbar_events(self, toolbar):
834  for event, callback, id in (
835  (wx.EVT_TOOL, self.OnOpenLogsDirectoryOnOpenLogsDirectory, ID_OPEN_LOGS_DIR),
836  (wx.EVT_TOOL, self.OnShowReportOnShowReport, ID_SHOW_REPORT),
837  (wx.EVT_TOOL, self.OnShowLogOnShowLog, ID_SHOW_LOG)):
838  toolbar.Bind(event, callback, id=id)
839 
840  for event, handler, source in (
841  (wx.EVT_CHECKBOX, self._on_autosave_cb_on_autosave_cb,
842  self.autosave_cbautosave_cb),
843  (wx.EVT_CHECKBOX, self._on_pause_on_failure_cb_on_pause_on_failure_cb,
844  self.pause_on_failure_cbpause_on_failure_cb),
845  (wx.EVT_CHOICE, self._on_profile_selection_on_profile_selection, self.choicechoice)):
846  toolbar.Bind(event, handler, source)
847 
848 
851  def _on_autosave_cb(self, evt):
852  self.save_setting("auto_save", evt.IsChecked())
853 
854  def _on_pause_on_failure_cb(self, evt):
855  self._pause_on_failure_pause_on_failure = evt.IsChecked()
856  self._test_runner_test_runner.send_pause_on_failure(evt.IsChecked())
857 
858  def _on_profile_selection(self, event):
859  self.save_setting("profile_name", event.GetString())
860  self._set_profile_set_profile(self.profile_name)
861 
862 
865  def _init_profile_choice(self, profile_name):
866  items = self.choicechoice.GetItems()
867  if profile_name not in items:
868  return
869  choice_index = items.index(profile_name)
870  self.choicechoice.Select(choice_index)
871  self._set_profile_set_profile(profile_name)
872 
873 
876  def _set_profile(self, profile_name):
877  profile = self._test_runner_test_runner.get_profile(profile_name)
878  self._profile_toolbar_profile_toolbar = profile.get_toolbar(self._config_panel_config_panel)
879 
880  if self._profile_toolbar_profile_toolbar:
881  sizer = self._config_panel_config_panel.GetSizer()
882  sizer.ShowItems(False)
883  sizer.Clear()
884  sizer.Add(self._profile_toolbar_profile_toolbar, 0, wx.EXPAND)
885  sizer.ShowItems(True)
886  self._config_panel_config_panel.Parent.Layout()
887 
889  return self._test_runner_test_runner.get_profile(self.choicechoice.GetStringSelection())
890 
892  self.panelpanel = wx.Panel(self.notebook)
893  self._mysettings_mysettings = RIDEDialog(parent=self.panelpanel)
894  self.panelpanel.SetBackgroundColour(self._mysettings_mysettings.color_background)
895  self.panelpanel.SetForegroundColour(self._mysettings_mysettings.color_foreground)
896  self._local_toolbar_local_toolbar = self._build_local_toolbar_build_local_toolbar(self.panelpanel)
897  self._runner_toolbar_runner_toolbar = self._build_runner_toolbar_build_runner_toolbar(self.panelpanel)
898  self._config_panel_config_panel = self._build_config_panel_build_config_panel(self.panelpanel)
899 
900  sizer = wx.BoxSizer(wx.VERTICAL)
901  sizer.Add(self._local_toolbar_local_toolbar, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 5)
902  sizer.Add(wx.StaticLine(self.panelpanel), 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 7)
903  sizer.Add(self._runner_toolbar_runner_toolbar, 0, wx.EXPAND | wx.ALL, 5)
904  sizer.Add(wx.StaticLine(self.panelpanel), 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 4)
905  sizer.Add(self._config_panel_config_panel, 0, wx.EXPAND, 5)
906  sizer.Add(wx.StaticLine(self.panelpanel), 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 4)
907  self._output_panel_output_panel = self._build_output_panel_build_output_panel(self.panelpanel)
908  sizer.Add(self._output_panel_output_panel, 1, wx.EXPAND | wx.TOP, 5)
909  self.panelpanel.SetSizer(sizer)
910 
911  self._process_timer_process_timer = wx.Timer(self.panelpanel)
912  self.panelpanel.Bind(wx.EVT_TIMER, self.OnTimerOnTimer)
913  self.panelpanel.Bind(wx.EVT_WINDOW_DESTROY, self.OnCloseOnClose)
914 
915  self.add_tab(self.panelpanel, self.titletitle, allow_closing=False)
916 
917 
920  def _build_config_panel(self, parent):
921  panel = wx.Panel(parent, wx.ID_ANY,
922  style=wx.BORDER_NONE | wx.TAB_TRAVERSAL)
923  panel.SetBackgroundColour(self._mysettings_mysettings.color_background)
924  panel.SetForegroundColour(self._mysettings_mysettings.color_foreground)
925  vertical_sizer = wx.BoxSizer(wx.VERTICAL)
926  panel.SetSizer(vertical_sizer)
927  return panel
928 
929  def _build_output_panel(self, parent):
930  panel = wx.Panel(parent)
931  panel.SetBackgroundColour(self._mysettings_mysettings.color_background)
932  panel.SetForegroundColour(self._mysettings_mysettings.color_foreground)
933  self._progress_bar_progress_bar = ProgressBar(panel, self.fail_colorfail_color, self.pass_colorpass_color, self.skip_colorskip_color)
934  self._console_log_panel, self._console_log_ctrl_console_log_ctrl = \
935  self._create_collapsible_pane_create_collapsible_pane(panel, 'Console log',
936  self.show_console_log,
937  self.OnConsoleLogPaneChangedOnConsoleLogPaneChanged)
938  self._message_log_panel, self._message_log_ctrl_message_log_ctrl = \
939  self._create_collapsible_pane_create_collapsible_pane(panel, 'Message log',
940  self.show_message_log,
941  self.OnMessageLogPaneChangedOnMessageLogPaneChanged)
942 
943  panel_sizer = wx.BoxSizer(wx.VERTICAL)
944  panel_sizer.Add(self._progress_bar_progress_bar, 0, wx.EXPAND | wx.BOTTOM, 10)
945  panel_sizer.Add(self._console_log_panel, int(self.show_console_log), wx.EXPAND)
946  panel_sizer.Add(self._message_log_panel, int(self.show_message_log), wx.EXPAND)
947  panel.SetSizer(panel_sizer)
948  return panel
949 
950  def OnConsoleLogPaneChanged(self, evt):
951  self.save_setting("show_console_log", not evt.Collapsed)
952  self._change_item_proportion_change_item_proportion(self._output_panel_output_panel,
953  self._console_log_panel,
954  int(not evt.Collapsed))
955  self._output_panel_output_panel.Layout()
956 
957  def OnMessageLogPaneChanged(self, evt):
958  self.save_setting("show_message_log", not evt.Collapsed)
959  self._change_item_proportion_change_item_proportion(self._output_panel_output_panel,
960  self._message_log_panel,
961  int(not evt.Collapsed))
962  self._output_panel_output_panel.Layout()
963 
964  @staticmethod
965  def _change_item_proportion(panel, item, proportion):
966  sizer = panel.GetSizer()
967  children = sizer.GetChildren()
968  for itemIndex in range(len(children)):
969  if item == children[itemIndex].Window:
970  sizer.Detach(item)
971  sizer.Insert(itemIndex, item, proportion, wx.EXPAND)
972  return
973 
974  def _create_collapsible_pane(self, parent, title, expand,
975  pane_changed_handler):
976  collapsible_pane = wx.CollapsiblePane(
977  parent, wx.ID_ANY, title,
978  style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
979  collapsible_pane.SetBackgroundColour(self._mysettings_mysettings.color_background)
980  collapsible_pane.SetForegroundColour(self._mysettings_mysettings.color_foreground)
981  if expand:
982  collapsible_pane.Expand()
983  collapsible_pane.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
984  pane_changed_handler,
985  collapsible_pane)
986 
987  pane = collapsible_pane.GetPane()
988  pane.SetBackgroundColour(self._mysettings_mysettings.color_background)
989  pane.SetForegroundColour(self._mysettings_mysettings.color_foreground)
990  text_ctrl = self._create_text_ctrl_create_text_ctrl(pane)
991  text_ctrl.SetBackgroundColour(self._mysettings_mysettings.color_background)
992  text_ctrl.SetForegroundColour(self._mysettings_mysettings.color_foreground)
993  vertical_sizer = wx.BoxSizer(wx.VERTICAL)
994  vertical_sizer.Add(text_ctrl, 1, wx.EXPAND)
995  pane.SetSizer(vertical_sizer)
996  return collapsible_pane, text_ctrl
997 
998  def _create_text_ctrl(self, parent):
999  text_ctrl = OutputStyledTextCtrl(parent)
1000  text_ctrl.SetScrollWidth(100)
1001  self._set_margins_set_margins(text_ctrl)
1002  text_ctrl.SetReadOnly(True)
1003  return text_ctrl
1004 
1005  @staticmethod
1006  def _create_check_box(parent, id, label, value, tooltip):
1007  cb = wx.CheckBox(parent, id, label)
1008  cb.SetToolTip(wx.ToolTip(tooltip))
1009  cb.SetValue(value)
1010  return cb
1011 
1012  @staticmethod
1013  def _set_margins(out):
1014  out.SetMarginLeft(10)
1015  out.SetMarginWidth(0, 0)
1016  out.SetMarginWidth(1, 0)
1017  out.SetMarginWidth(2, 0)
1018  out.SetMarginWidth(3, 0)
1019 
1020 
1028  def _test_runner_events_handler(self, event, *args):
1029  if not self.panelpanel:
1030  # this should only happen if the notebook tab got deleted
1031  # out from under us. In the immortal words of Jar Jar
1032  # Binks, "How rude!"
1033  return
1034  if event == 'start_test':
1035  self._handle_start_test_handle_start_test(args)
1036  return
1037  if event == 'end_test':
1038  self._handle_end_test_handle_end_test(args)
1039  return
1040  if event == 'report_file':
1041  self._handle_report_file_handle_report_file(args)
1042  return
1043  if event == 'log_file':
1044  self._handle_log_file_handle_log_file(args)
1045  return
1046  if event == 'start_keyword':
1047  self._handle_start_keyword_handle_start_keyword(args)
1048  return
1049  if event == 'end_keyword':
1050  self._handle_end_keyword_handle_end_keyword()
1051  return
1052  if event == 'log_message':
1053  self._handle_log_message_handle_log_message(args)
1054  return
1055  if event == 'paused':
1056  self._handle_paused_handle_paused(args)
1057  return
1058  if event == 'continue':
1059  self._handle_continue_handle_continue(args)
1060 
1061  def _handle_start_test(self, args):
1062  longname = args[1]['longname'].encode('utf-8')
1063  self._log_message_queue_log_message_queue.put(
1064  f"Starting test: {longname.decode(encoding['OUTPUT'], 'backslashreplace')}")
1065 
1066  def _handle_end_test(self, args):
1067  longname = args[1]['longname'].encode('utf-8')
1068  self._log_message_queue_log_message_queue.put(
1069  f"Ending test: {longname.decode(encoding['OUTPUT'], 'backslashreplace')}\n")
1070  if args[1]['status'] == 'PASS':
1071  self._progress_bar_progress_bar.add_pass()
1072  elif args[1]['status'] == 'SKIP':
1073  self._progress_bar_progress_bar.add_skip()
1074  elif args[1]['status'] == 'FAIL':
1075  self._progress_bar_progress_bar.add_fail()
1076  else:
1077  self._log_message_queue_log_message_queue.put(f"UNKNOWN STATUS: {args[1]['status']}\n")
1078 
1079  def _handle_report_file(self, args):
1080  self._report_file_report_file = args[0]
1081  wx.CallAfter(self._local_toolbar_local_toolbar.EnableTool, ID_SHOW_REPORT, True)
1082 
1083  def _handle_log_file(self, args):
1084  self._log_file_log_file = args[0]
1085  wx.CallAfter(self._local_toolbar_local_toolbar.EnableTool, ID_SHOW_LOG, True)
1086 
1087  def _handle_start_keyword(self, args):
1088  self._progress_bar_progress_bar.set_current_keyword(args[0])
1089 
1091  self._progress_bar_progress_bar.empty_current_keyword()
1092 
1093  def _handle_log_message(self, args):
1094  a = args[0]
1095  if LOG_LEVELS[a['level']] >= self._min_log_level_number_min_log_level_number:
1096  prefix = '%s : %s : ' % (a['timestamp'], a['level'].rjust(5))
1097  message = a['message']
1098  if '\n' in message:
1099  message = '\n' + message
1100  self._log_message_queue_log_message_queue.put(prefix + message)
1101 
1102  def _handle_paused(self, args):
1103  wx.CallAfter(self._set_paused_set_paused)
1104  self._log_message_queue_log_message_queue.put('<< PAUSED >>')
1105 
1106  def _handle_continue(self, args):
1107  wx.CallAfter(self._set_continue_set_continue)
1108  self._log_message_queue_log_message_queue.put('<< CONTINUE >>')
1109 
1110  def _set_running(self):
1111  self._run_action_run_action.disable()
1112  self._stop_action_stop_action.enable()
1113  self._enable_runner_toolbar_enable_runner_toolbar(False, True)
1114  self.get_current_profileget_current_profile().disable_toolbar()
1115  self._running_running = True
1116  self._test_runner_test_runner.test_execution_started()
1117 
1118  def _set_paused(self):
1119  self._run_action_run_action.disable()
1120  self._stop_action_stop_action.enable()
1121  self._enable_runner_toolbar_enable_runner_toolbar(False, False)
1122 
1123  def _set_continue(self):
1124  self._run_action_run_action.disable()
1125  self._stop_action_stop_action.enable()
1126  self._enable_runner_toolbar_enable_runner_toolbar(False, True)
1127 
1128  def _set_stopped(self):
1129  self._run_action_run_action.enable()
1130  self._stop_action_stop_action.disable()
1131  self._enable_runner_toolbar_enable_runner_toolbar(True, False)
1132  self.get_current_profileget_current_profile().enable_toolbar()
1133  self._running_running = False
1134 
1135  def _enable_runner_toolbar(self, run, paused):
1136  stop = not run
1137  debug = stop and not paused
1138  for id, enabled in ((ID_RUN, run),
1139  (ID_RUNDEBUG, run),
1140  (ID_STOP, stop),
1141  (ID_PAUSE, paused),
1142  (ID_CONTINUE, debug),
1143  (ID_STEP_NEXT, debug),
1144  (ID_STEP_OVER, debug)):
1145  self._runner_toolbar_runner_toolbar.EnableTool(id, enabled)
1146 
1147  @staticmethod
1149  wx.MessageBox("There isn't logs directory. \n"
1150  "Please, run the tests and try again",
1151  "No logs directory",
1152  wx.ICON_INFORMATION | wx.OK)
1153 
1154 
1155 
1156 class ProgressBar(wx.Panel):
1157 
1158  def __init__(self, parent, fail_color='#FF8E8E', pass_color="#9FCC9F", skip_color='yellow'):
1159  wx.Panel.__init__(self, parent, wx.ID_ANY)
1160  self._sizer_sizer = wx.BoxSizer(wx.HORIZONTAL)
1161  self._gauge_gauge = wx.Gauge(self, size=(100, 10))
1162  self._label_label = Label(self)
1163  self._sizer_sizer.Add(self._label_label, 1, wx.EXPAND | wx.LEFT, 10)
1164  self._sizer_sizer.Add(self._gauge_gauge, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 10)
1165  self._sizer_sizer.Layout()
1166  self.SetSizer(self._sizer_sizer)
1167  self._gauge_gauge.Hide()
1168  self._default_colour_default_colour = parent.GetBackgroundColour()
1169  self._foreground_colour_foreground_colour = parent.GetForegroundColour()
1170  self.fail_colorfail_color = fail_color
1171  self.pass_colorpass_color = pass_color
1172  self.skip_colorskip_color = skip_color
1173  self._timer_timer = wx.Timer(self)
1174  self.Bind(wx.EVT_TIMER, self.OnTimerOnTimer)
1175  self._initialize_state_initialize_state()
1176 
1178  self._pass_pass = 0
1179  self._fail_fail = 0
1180  self._skip_skip = 0
1181  self._current_keywords_current_keywords = []
1182 
1183  def set_current_keyword(self, name):
1184  self._current_keywords_current_keywords.append(name)
1185 
1187  if self._current_keywords_current_keywords:
1188  self._current_keywords_current_keywords.pop()
1189 
1190 
1191  def OnTimer(self, event):
1192  self._gauge_gauge.Show()
1193  self._gauge_gauge.Pulse()
1194  self._update_message_update_message()
1195 
1196 
1197  def Start(self):
1198  self._initialize_state_initialize_state()
1199  self._start_time_start_time = time.time()
1200  self._gauge_gauge.Show()
1201  self._sizer_sizer.Layout()
1202  self.SetBackgroundColour(self._default_colour_default_colour)
1203  self.SetForegroundColour(self._foreground_colour_foreground_colour)
1204  self._timer_timer.Start(50)
1205 
1206 
1207  def Stop(self):
1208  self._gauge_gauge.Hide()
1209  self._timer_timer.Stop()
1210 
1211 
1212  def add_pass(self):
1213  self._pass_pass += 1
1214 
1215 
1216  def add_fail(self):
1217  self._fail_fail += 1
1218 
1219 
1220  def add_skip(self):
1221  self._skip_skip += 1
1222 
1223  def get_visible_color(self, color):
1224  color_diff = wx.Colour.GetRGBA(wx.Colour(color)) - wx.Colour.GetRGBA(self._foreground_colour_foreground_colour)
1225  # print(f"DEBUG: get_visible_color color={wx.Colour.GetRGBA(wx.Colour(color)):0x} default={wx.Colour.GetRGBA(self._foreground_colour):0x}"
1226  # f" color_diff={wx.Colour.GetRGBA(wx.Colour(color_diff)):0x} white={wx.Colour.GetRGBA(wx.Colour('white')):0x}"
1227  # f" black={wx.Colour.GetRGBA(wx.Colour('black')):0x} gray={wx.Colour.GetRGBA(wx.Colour('gray')):0x}")
1228  if wx.Colour.GetRGBA(wx.Colour(color)) > wx.Colour.GetRGBA(self._foreground_colour_foreground_colour) > wx.Colour.GetRGBA(wx.Colour('gray')):
1229  if color_diff > wx.Colour.GetRGBA(wx.Colour('gray')):
1230  return wx.Colour(self._foreground_colour_foreground_colour)
1231  return wx.Colour('black')
1232 
1233 
1234 
1237  def _update_message(self):
1238  elapsed = time.time() - self._start_time_start_time
1239  message = "elapsed time: %s pass: %s skip: %s fail: %s" % (
1240  self._seconds_to_string_seconds_to_string(elapsed), self._pass_pass, self._skip_skip, self._fail_fail)
1241  message += self._get_current_keyword_text_get_current_keyword_text()
1242  self._label_label.SetLabel(message)
1243  if self._fail_fail > 0:
1244  self.SetForegroundColour(self.get_visible_colorget_visible_color(self.fail_colorfail_color))
1245  self.SetBackgroundColour(self.fail_colorfail_color)
1246  elif self._skip_skip > 0:
1247  self.SetForegroundColour(self.get_visible_colorget_visible_color(self.skip_colorskip_color))
1248  self.SetBackgroundColour(self.skip_colorskip_color)
1249  elif self._pass_pass > 0:
1250  self.SetForegroundColour(self.get_visible_colorget_visible_color(self.pass_colorpass_color))
1251  self.SetBackgroundColour(self.pass_colorpass_color)
1252  # not sure why this is required, but without it the background
1253  # colors don't look right on Windows
1254  self.Refresh()
1255 
1257  if not self._current_keywords_current_keywords:
1258  return ''
1259  return ' current keyword: ' + \
1260  self._fix_size_fix_size(' -> '.join(self._current_keywords_current_keywords), 50)
1261 
1262  @staticmethod
1263  def _fix_size(text, max_length):
1264  if len(text) <= max_length:
1265  return text
1266  return '...' + text[3 - max_length:]
1267 
1268  # stole this off the internet. Nifty.
1269  @staticmethod
1270 
1274  return "%d:%02d:%02d" % \
1275  reduce(lambda ll, b: divmod(ll[0], b) + ll[1:], [(t,), 60, 60])
1276 
1277 
1279 
1280  def __init__(self, parent):
1281  wx.stc.StyledTextCtrl.__init__(self, parent, wx.ID_ANY,
1282  style=wx.SUNKEN_BORDER)
1283  app_settings = self._get_app_settings_get_app_settings(parent)
1284  self.stylizerstylizer = OutputStylizer(self, app_settings)
1285  self._max_row_len_max_row_len = 0
1286 
1287  def update_scroll_width(self, string):
1288  if isinstance(string, bytes):
1289  linesep = b'\n'
1290  else:
1291  linesep = '\n'
1292  string_max_len = max(len(s) for s in string.split(linesep))
1293  if string_max_len <= self._max_row_len_max_row_len:
1294  return
1295  self._max_row_len_max_row_len = string_max_len
1296  try:
1297  width, _ = self.GetTextExtent(string)
1298  if self.GetScrollWidth() < width + 50:
1299  self.SetScrollWidth(width + 50)
1300  except UnicodeDecodeError:
1301  pass
1302 
1303  @staticmethod
1304  def _get_app_settings(parent):
1305  while True:
1306  if not parent:
1307  raise ValueError('Value does not contain NoteBook as parent')
1308  if isinstance(parent, NoteBook):
1309  return parent._app.settings
1310  parent = parent.GetParent()
1311 
1312 
1314  def __init__(self, editor, settings):
1315  self.editoreditor = editor
1316  self.settingssettings = settings._config_obj['Plugins']['Test Runner']
1317  self._ensure_default_font_is_valid_ensure_default_font_is_valid()
1318  self._set_styles_set_styles()
1319  PUBLISHER.subscribe(self.OnSettingsChangedOnSettingsChanged, RideSettingsChanged)
1320 
1321 
1322  def OnSettingsChanged(self, message):
1323  section, setting = message.keys
1324  if section == 'Test Runner':
1325  self._set_styles_set_styles()
1326 
1327 
1330  def _set_styles(self):
1331  background = self.settingssettings.get('background', 'white')
1332  font_size = self.settingssettings.get('font size', 10)
1333  font_face = self.settingssettings.get('font face', 'Courier New')
1334  self.fail_colorfail_color = self.settingssettings.get('fail color', '#FF8E8E')
1335  self.pass_colorpass_color = self.settingssettings.get('pass color', '#9FCC9F')
1336  self.skip_colorskip_color = self.settingssettings.get('skip color', 'yellow')
1337 
1338  default_style = self._get_style_string_get_style_string(
1339  fore=self.settingssettings.get('foreground', 'black'), back=background,
1340  size=font_size, face=font_face)
1341  error_style = self._get_style_string_get_style_string(
1342  fore=self.settingssettings.get('error', 'red'), back=background,
1343  size=font_size, face=font_face)
1344  fail_style = self._get_style_string_get_style_string(fore=self.fail_colorfail_color, back=background, size=font_size, face=font_face)
1345  pass_style = self._get_style_string_get_style_string(fore=self.pass_colorpass_color, back=background, size=font_size, face=font_face)
1346  skip_style = self._get_style_string_get_style_string(fore=self.skip_colorskip_color, back=background, size=font_size, face=font_face)
1347 
1348  self.editoreditor.StyleSetSpec(STYLE_DEFAULT, default_style)
1349  self.editoreditor.StyleSetSpec(STYLE_STDERR, error_style)
1350  self.editoreditor.StyleSetSpec(STYLE_FAIL, fail_style)
1351  self.editoreditor.StyleSetSpec(STYLE_PASS, pass_style)
1352  self.editoreditor.StyleSetSpec(STYLE_SKIP, skip_style)
1353  self.editoreditor.StyleSetSpec(7, error_style)
1354  self.editoreditor.StyleSetBackground(wx.stc.STC_STYLE_DEFAULT, background)
1355  self.editoreditor.Refresh()
1356 
1357  @staticmethod
1358  def _get_style_string(back, fore, size, face):
1359  return ','.join('%s:%s' % (name, value)
1360  for name, value in locals().items() if value)
1361 
1362 
1366  default_font = self.settingssettings.get('font face')
1367  if default_font not in ReadFonts():
1368  sys_font = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT)
1369  self.settingssettings['font face'] = sys_font.GetFaceName()
1370 
1371 
1372 Robot = PyEmbeddedImage(
1373  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAnNJ"
1374  "REFUOI2Vkb1Pk3EQxz/PW6EvUN6sEQFBIwUlMBgTMZFZJzcXEzeJiXE1MXFi4g8gGhjcHDA4"
1375  "iFGDKNFojBoJaqQItgrlpYUW0ZZSaJ/ndw5INQZIvMttd5/73vcQEbYrpRSPes5K7NsrUaK2"
1376  "7RERdHaJnLeV4tL9u7XsDNA0qKhrw19erf0nQABBRBEeGyT86YUgIKjtF4nIP+PC0tsRGb11"
1377  "g+hcnAqvl6ZjrQQ7r664ygIV/8opAATIpr53fui53psZfoqsZcn5TEyXjlrPQcNBvMdO0XG5"
1378  "S4M/GPNvWnQ23Ptg4hW1xxsxLAssE0MHHIWgM/f+Me35a1iWmy1IASCOw+f+XhwMQuML/Eik"
1379  "WVA6mlLU6A7+AwEqKxSjN7vlxJUubUtEwcTJ8XF5PfAA23ZIJTMkppdoathLS7CO5EyS1M8M"
1380  "GjpDdwcR/vhWUHAo2KjtaWmWeWeJtlNH0DqamPwSxTQtTl88g21nWUlG6bhwficThWQsKpfO"
1381  "tWMkBFGQXc9j6RYuw8F0WXgOe+i7F9LQTLZu0Au/V8Lzh32UFBfjK3dRWlVEoMaDf59JSbUH"
1382  "d5ULv7uI+7e7RZT9+2+gC5sZ/Tom4U/P8PgMViVHWjZYNxxsl7Bh2uDTCFT7+Dw2ROjdw9/C"
1383  "BfN7fEp+LLxkMrxIKp0mGDxAc8s6dXvrQRc0TUfTYSocxs7rxBOrfHxzh3J/Tvz7TmImYhMs"
1384  "Rl4zG1lDicOT4RBHWyr5GBrH0DcvdGxFWUme+Zk0tY2lzM3NshyfxHDXo0fCEQb6R4hMx3Bs"
1385  "hTiCKMFtpsmoLHl7Ga8fRATHEcRRrCxnGBocIR6L8Qu2hlAKJu0L3QAAAABJRU5ErkJggg==")
1386 getRobotBitmap = Robot.GetBitmap
1387 
1388 MenuButton = PyEmbeddedImage(
1389  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAKxJ"
1390  "REFUOI3t0jEKg0AUBNAxhmXX9QD2adLnJt7E2luIeB/PkCoQCG5lK8ifdZtNHyQRLGwy5Yd5"
1391  "/GKSGCP25LSr/QcAAOfPQ9/3MYSAZVngvQdJiAhEhFVVZT8BkpKmaZbnOZRS0FojhIBpmh6b"
1392  "Ppjn+ULyqZSyxhiM44hhGEiyXAOStSG1bVuIyMtaq51zJHltmsZtBgCgruuC5N17f+u6brX8"
1393  "Fdia43dwPPAGncZYbvceeuMAAAAASUVORK5CYII=")
1394 getMenuButtonBitmap = MenuButton.GetBitmap
1395 
1396 ProcessStop = PyEmbeddedImage(
1397  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0"
1398  "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJJSURBVDjLpZNNbxJRFIb7A/wF"
1399  "/A5YunRDovsmRk3cmLAxcdG0uiFuXDSmkBlLFNOmtYFKgibUtqlJG6UjiGksU0oZPgQs0KEw"
1400  "Mw4Dw8dQjnPuMCNq48abvJub87zn4547BQBTk7q2CDZdDl1OXdNjOcd3tj/jJ8Eruuxzb2RX"
1401  "+NMpHT/MMUfHJwKbSgv7Bxnm9YciPRMSXRiDsb8ZjOGrwWjNzZ4UOL4pg6IOQLsYEbU6fajW"
1402  "RYgdpLilnYIbY00T08COcCrzTen2NMCj9ocgKgMQdLV7Q3KnqH3YTyQV/1YWTezEAPvCsjGz"
1403  "CTfkPtR/9IGXDNWkHlTFnmWysxfj7q/x2I4NDRxh5juNZf8LPm12ifBkimdAheI0smjgjH3N"
1404  "MtgzlmqCNx5tGnq4Abe9LIHLjS7IHQ3OJRWW1zcYZNFgOnl0LOCwmq0BgTEjgqbQoHSuQrGu"
1405  "EqO+dgFrgXUBWWJwyKaIAZaPcEXoWvD1uQjc8rBQ4FUio4oBLK+8sgycH7+kGUnpQUvVrF4x"
1406  "K4KomwuGQf6sQ14mV5GA8gesFhyB3TxdrjZhNAKSwSzXzIpgrtaBbLUDg+EI9j6nwe3btIZo"
1407  "exBsuHajCU6QjSlfBmaqbZIgr2f3Pl/l7vpyxjOai0S9Zd2R91GFF41Aqa1Z1eAyYeZcRQSP"
1408  "P6jMUlu/FmlylecDCfdqKMLFk3ko8zKZCfacLgmwHWVhnlriZrzv/l7lyc9072XJ9fjFNv10"
1409  "cYWhnvmEBS8tPPH4mVlPmL5DZy7/TP/znX8C6zgR9sd1gukAAAAASUVORK5CYII=")
1410 getProcessStopBitmap = ProcessStop.GetBitmap
1411 
1412 # page_white.png from http://www.famfamfam.com/lab/icons/silk
1413 ReportIcon = PyEmbeddedImage(
1414  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0"
1415  "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC4SURBVCjPdZFbDsIgEEWnrsMm"
1416  "7oGGfZrohxvU+Iq1TyjU60Bf1pac4Yc5YS4ZAtGWBMk/drQBOVwJlZrWYkLhsB8UV9K0BUrP"
1417  "Gy9cWbng2CtEEUmLGppPjRwpbixUKHBiZRS0p+ZGhvs4irNEvWD8heHpbsyDXznPhYFOyTjJ"
1418  "c13olIqzZCHBouE0FRMUjA+s1gTjaRgVFpqRwC8mfoXPPEVPS7LbRaJL2y7bOifRCTEli3U7"
1419  "BMWgLzKlW/CuebZPAAAAAElFTkSuQmCC")
1420 getReportIconBitmap = ReportIcon.GetBitmap
1421 
1422 # page_white_text.png from http://www.famfamfam.com/lab/icons/silk
1423 LogIcon = PyEmbeddedImage(
1424  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0"
1425  "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2cee"
1426  "gTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMj"
1427  "IyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9O"
1428  "Tlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH"
1429  "+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0"
1430  "lc9eTHYTAAAAAElFTkSuQmCC")
1431 getLogIconBitmap = LogIcon.GetBitmap
1432 
1433 # bug.png from http://www.famfamfam.com/lab/icons/silk
1434 BugIcon = PyEmbeddedImage(
1435  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0"
1436  "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKYSURBVDjLnZPJT1NRFMb5G1wD"
1437  "HV5boNiqdHrvFYolCAtsGSSWKpMFKhYqlDI6oAEKaVJwCIgSphaKtLYWCgSNBgRjMNHoxsSF"
1438  "S3cmJmA0NMTw+R6JKKZl4eJL7sm953fOd3JPHIC4WMpcppG5SGnZc8ZjVVF6QLn975sDgfaZ"
1439  "mvg71oRJZIRUYcuAnq/2KWroGfm3QwEn2YpLVPPvOD2oiqj9yq/mGznegl56mx6T7ZbY1M6Y"
1440  "AM0CuZkxT0b2Wg6QW/SsApRXDsotR+d6E9Y/h9DuqoCuJq0lKoDxqU1/pITGR27mBU4h+GEc"
1441  "Tz5OY+ClA5JbyahYzof/9TBO9B/FcWcqpA4xU3We3GJ87ntnfO5meinMvruNnqcmXA2XoDVc"
1442  "Cc0wCYkzBaZpA7ILRJ/2O2B87jA+QT9UeDRe8svZYAG8b/txc6kc9mA+yqayYPQXwvdmBEOr"
1443  "A5B2p0BtFIYOWKCm5RukWwZyXIbA+0F0LpaiKaBHmVsLw4we99ccsM8a8GClF5JOMcQdou8p"
1444  "rULrgRmQo7KI0VcE13MrGv06lE5kodhzGvdWu2GdKkTVWC4DcELcJkKyXbCb1EhAVM//M0DV"
1445  "UNqP2qAJd1baUDaZjTMTeXAttsPi0cM0mgvHvA0NkxYk2QRIrieOsDmEmXttH0DfVfSluSTo"
1446  "WmpD8bgOroUOWNw6VI7koGfOBuq6EqLLTNU6ojrmP5D1HVsjmrkYezGIrlA9LjKgnrlGXJlp"
1447  "gbCOD0EtD0QNN8I3cZqjAlhJr4rXpB1iNLhrYffUQWoT7yUKzbxqJlHLq0jc5JYmgHMunogK"
1448  "YJVqF7mTrPyfgktMRTMX/CrOq1gLF3fYNrLiX+Bs8MoTwT2fQPwXgBXHGL+TaIjfinb3C7cs"
1449  "cRMIcYL6AAAAAElFTkSuQmCC")
1450 getBugIconBitmap = BugIcon.GetBitmap
Base class for all test runner profiles.
Definition: runprofiles.py:59
def _ensure_default_font_is_valid(self)
Checks if default font is installed.
def OnSettingsChanged(self, message)
Redraw colors and font if settings are modified.
A progress bar for the test runner plugin.
def Start(self)
Signals the start of a test run; initialize progressbar.
def OnTimer(self, event)
A handler for timer events; it updates the statusba.
def _seconds_to_string(t)
Convert a number of seconds to a string of the form HH:MM:SS.
def __init__(self, parent, fail_color='#FF8E8E', pass_color="#9FCC9F", skip_color='yellow')
def _update_message(self)
Update the displayed elapsed time, passed and failed counts.
A plugin for running tests from within RIDE.
def _on_autosave_cb(self, evt)
Called when the user clicks on the "Auto Save" checkbox.
def _remove_from_notebook(self)
Remove the tab for this plugin from the notebook.
def OnShowLog(self, evt)
Called when the user clicks on the "Log" button.
def OnClose(self, event)
Shut down the running services and processes.
def _get_last_output_char(self)
Return the last character in the output window.
def OnRunDebug(self, event)
Called when the user clicks or presses the F9, Run Tests with Debug It can still be overwritten in RI...
def _create_collapsible_pane(self, parent, title, expand, pane_changed_handler)
def _initialize_variables_for_running(self, profile_settings, args)
def _test_runner_events_handler(self, event, *args)
Endpoint of the listener interface.
def _set_profile(self, profile_name)
Set the profile to be used to run tests.
def _append_text(self, text_ctrl, text, source="stdout")
def OnStop(self, event)
Called when the user clicks the "Stop" button.
def _create_command_args(self, profile_command_args, log_level='INFO', use_colors=False)
def OnOpenLogsDirectory(self, evt)
Called when the user clicks on the "Open Logs Directory" button.
def _build_config_panel(self, parent)
Builds the configuration panel for this plugin.
def OnShowReport(self, evt)
Called when the user clicks on the "Report" button.
def _append_to_console_log(self, text, source="stdout")
Put output to the text control.
def OnRun(self, event)
Called when the user clicks or presses the F8, Run Tests.
def _init_profile_choice(self, profile_name)
First installation of the profile to be used to run tests.
def localize_shortcuts(string)
Definition: shortcut.py:57
def ReadFonts(fixed=False)
Returns list with fixed width fonts.
Definition: editors.py:43