summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lldb/test/lldbcurses.py196
-rw-r--r--lldb/test/test_results.py118
2 files changed, 185 insertions, 129 deletions
diff --git a/lldb/test/lldbcurses.py b/lldb/test/lldbcurses.py
index f1530c29543..cd9c89c86e7 100644
--- a/lldb/test/lldbcurses.py
+++ b/lldb/test/lldbcurses.py
@@ -1,5 +1,6 @@
-import time
import curses, curses.panel
+import sys
+import time
class Point(object):
def __init__(self, x, y):
@@ -57,9 +58,67 @@ class Rect(object):
return False
class Window(object):
- def __init__(self, window):
+ def __init__(self, window, delegate = None, can_become_first_responder = True):
self.window = window
+ self.parent = None
+ self.delegate = delegate
+ self.children = list()
+ self.first_responder = None
+ self.can_become_first_responder = can_become_first_responder
+
+ def add_child(self, window):
+ self.children.append(window)
+ window.parent = self
+
+ def remove_child(self, window):
+ self.children.remove(window)
+
+ def set_first_responder(self, window):
+ if window.can_become_first_responder:
+ if callable(getattr(window, "hidden", None)) and window.hidden():
+ return False
+ if not window in self.children:
+ self.add_child(window)
+ self.first_responder = window
+ return True
+ else:
+ return False
+
+ def resign_first_responder(self, remove_from_parent, new_first_responder):
+ success = False
+ if self.parent:
+ if self.is_first_responder():
+ self.parent.first_responder = None
+ success = True
+ if remove_from_parent:
+ self.parent.remove_child(self)
+ if new_first_responder:
+ self.parent.set_first_responder(new_first_responder)
+ else:
+ self.parent.select_next_first_responder()
+ return success
+ def is_first_responder(self):
+ if self.parent:
+ return self.parent.first_responder == self
+ else:
+ return False
+
+ def select_next_first_responder(self):
+ num_children = len(self.children)
+ if num_children == 1:
+ return self.set_first_responder(self.children[0])
+ for (i,window) in enumerate(self.children):
+ if window.is_first_responder():
+ break
+ if i < num_children:
+ for i in range(i+1,num_children):
+ if self.set_first_responder(self.children[i]):
+ return True
+ for i in range(0, i):
+ if self.set_first_responder(self.children[i]):
+ return True
+
def point_in_window(self, pt):
size = self.get_size()
return pt.x >= 0 and pt.x < size.w and pt.y >= 0 and pt.y < size.h
@@ -76,8 +135,18 @@ class Window(object):
except:
pass
- def box(self):
- self.window.box()
+ def attron(self, attr):
+ return self.window.attron (attr)
+
+ def attroff(self, attr):
+ return self.window.attroff (attr)
+
+ def box(self, vertch=0, horch=0):
+ if vertch == 0:
+ vertch = curses.ACS_VLINE
+ if horch == 0:
+ horch = curses.ACS_HLINE
+ self.window.box(vertch, horch)
def get_contained_rect(self, top_inset=0, bottom_inset=0, left_inset=0, right_inset=0, height=-1, width=-1):
'''Get a rectangle based on the top "height" lines of this window'''
@@ -111,20 +180,78 @@ class Window(object):
return Size(w=x, h=y)
def refresh(self):
+ self.update()
curses.panel.update_panels()
return self.window.refresh()
def resize(self, size):
- return window.resize(size.h, size.w)
+ return self.window.resize(size.h, size.w)
+ def timeout(self, timeout_msec):
+ return self.window.timeout(timeout_msec)
+
+ def handle_key(self, key, check_parent=True):
+ '''Handle a key press in this window.'''
+
+ # First try the first responder if this window has one, but don't allow
+ # it to check with its parent (False second parameter) so we don't recurse
+ # and get a stack overflow
+ if self.first_responder:
+ if self.first_responder.handle_key(key, False):
+ return True
+
+ # Check if the window delegate wants to handle this key press
+ if self.delegate:
+ if callable(getattr(self.delegate, "handle_key", None)):
+ if self.delegate.handle_key(self, key):
+ return True
+ if self.delegate(self, key):
+ return True
+ # Check if we have a parent window and if so, let the parent
+ # window handle the key press
+ if check_parent and self.parent:
+ return self.parent.handle_key(key, True)
+ else:
+ return False # Key not handled
+
+ def update(self):
+ for child in self.children:
+ child.update()
+
+ def key_event_loop(self, timeout_msec=-1, n=sys.maxint):
+ '''Run an event loop to receive key presses and pass them along to the
+ responder chain.
+
+ timeout_msec is the timeout it milliseconds. If the value is -1, an
+ infinite wait will be used. It the value is zero, a non-blocking mode
+ will be used, and if greater than zero it will wait for a key press
+ for timeout_msec milliseconds.
+
+ n is the number of times to go through the event loop before exiting'''
+ self.timeout(timeout_msec)
+ while n > 0:
+ c = self.window.getch()
+ if c != -1:
+ self.handle_key(c)
+ n -= 1
+
class Panel(Window):
- def __init__(self, frame):
+ def __init__(self, frame, delegate = None, can_become_first_responder = True):
window = curses.newwin(frame.size.h,frame.size.w, frame.origin.y, frame.origin.x)
- super(Panel, self).__init__(window)
+ super(Panel, self).__init__(window, delegate, can_become_first_responder)
self.panel = curses.panel.new_panel(window)
+ def hide(self):
+ return self.panel.hide()
+
+ def hidden(self):
+ return self.panel.hidden()
+
+ def show(self):
+ return self.panel.show()
+
def top(self):
- self.panel.top()
+ return self.panel.top()
def set_position(self, pt):
self.panel.move(pt.y, pt.x)
@@ -136,11 +263,12 @@ class Panel(Window):
self.set_position(new_position)
class BoxedPanel(Panel):
- def __init__(self, frame, title):
- super(BoxedPanel, self).__init__(frame)
+ def __init__(self, frame, title, delegate = None, can_become_first_responder = True):
+ super(BoxedPanel, self).__init__(frame, delegate, can_become_first_responder)
self.title = title
self.lines = list()
self.first_visible_idx = 0
+ self.selected_idx = -1
self.update()
def get_usable_width(self):
@@ -174,6 +302,42 @@ class BoxedPanel(Panel):
if update:
self.update()
+ def scroll_begin (self):
+ self.first_visible_idx = 0
+ if len(self.lines) > 0:
+ self.selected_idx = 0
+ else:
+ self.selected_idx = -1
+ self.update()
+
+ def scroll_end (self):
+ max_visible_lines = self.get_usable_height()
+ num_lines = len(self.lines)
+ if max_visible_lines > num_lines:
+ self.first_visible_idx = num_lines - max_visible_lines
+ else:
+ self.first_visible_idx = 0
+ self.selected_idx = num_lines-1
+ self.update()
+
+ def select_next (self):
+ self.selected_idx += 1
+ if self.selected_idx >= len(self.lines):
+ self.selected_idx = len(self.lines) - 1
+ self.update()
+
+ def select_prev (self):
+ self.selected_idx -= 1
+ if self.selected_idx < 0:
+ if len(self.lines) > 0:
+ self.selected_idx = 0
+ else:
+ self.selected_idx = -1
+ self.update()
+
+ def get_selected_idx(self):
+ return self.selected_idx
+
def _adjust_first_visible_line(self):
num_lines = len(self.lines)
max_visible_lines = self.get_usable_height()
@@ -199,20 +363,30 @@ class BoxedPanel(Panel):
def update(self):
self.erase()
+ is_first_responder = self.is_first_responder()
+ if is_first_responder:
+ self.attron (curses.A_REVERSE)
self.box()
+ if is_first_responder:
+ self.attroff (curses.A_REVERSE)
if self.title:
self.addstr(Point(x=2, y=0), ' ' + self.title + ' ')
max_width = self.get_usable_width()
for line_idx in range(self.first_visible_idx, len(self.lines)):
pt = self.get_point_for_line(line_idx)
if pt.is_valid_coordinate():
+ is_selected = line_idx == self.selected_idx
+ if is_selected:
+ self.attron (curses.A_REVERSE)
self.addnstr(pt, self.lines[line_idx], max_width)
+ if is_selected:
+ self.attroff (curses.A_REVERSE)
else:
return
class StatusPanel(Panel):
def __init__(self, frame):
- super(StatusPanel, self).__init__(frame)
+ super(StatusPanel, self).__init__(frame, delegate=None, can_become_first_responder=False)
self.status_items = list()
self.status_dicts = dict()
self.next_status_x = 1
diff --git a/lldb/test/test_results.py b/lldb/test/test_results.py
index 31b5090e81e..c2616df7da9 100644
--- a/lldb/test/test_results.py
+++ b/lldb/test/test_results.py
@@ -900,124 +900,6 @@ class RawPickledFormatter(ResultsFormatter):
self.out_file.send(
"{}#{}".format(len(pickled_message), pickled_message))
-class Curses(ResultsFormatter):
- """Receives live results from tests that are running and reports them to the terminal in a curses GUI"""
-
- def clear_line(self, y):
- self.out_file.write("\033[%u;0H\033[2K" % (y))
- self.out_file.flush()
-
- def print_line(self, y, str):
- self.out_file.write("\033[%u;0H\033[2K%s" % (y, str))
- self.out_file.flush()
-
- def __init__(self, out_file, options):
- # Initialize the parent
- super(Curses, self).__init__(out_file, options)
- self.using_terminal = True
- self.have_curses = True
- self.initialize_event = None
- self.jobs = [None] * 64
- self.job_tests = [None] * 64
- try:
- import lldbcurses
- self.main_window = lldbcurses.intialize_curses()
- self.main_window.refresh()
- except:
- self.have_curses = False
- lldbcurses.terminate_curses()
- self.using_terminal = False
- print "Unexpected error:", sys.exc_info()[0]
- raise
-
-
- self.line_dict = dict()
- self.events_file = open("/tmp/events.txt", "w")
- # self.formatters = list()
- # if tee_results_formatter:
- # self.formatters.append(tee_results_formatter)
-
- def status_to_short_str(self, status):
- if status == 'success':
- return '.'
- elif status == 'failure':
- return 'F'
- elif status == 'unexpected_success':
- return '?'
- elif status == 'expected_failure':
- return 'X'
- elif status == 'skip':
- return 'S'
- elif status == 'error':
- return 'E'
- else:
- return status
- def handle_event(self, test_event):
- with self.lock:
- super(Curses, self).handle_event(test_event)
- # for formatter in self.formatters:
- # formatter.process_event(test_event)
- if self.have_curses:
- import lldbcurses
- worker_index = -1
- if 'worker_index' in test_event:
- worker_index = test_event['worker_index']
- if 'event' in test_event:
- print >>self.events_file, str(test_event)
- event = test_event['event']
- if event == 'test_start':
- name = test_event['test_class'] + '.' + test_event['test_name']
- self.job_tests[worker_index] = test_event
- if 'pid' in test_event:
- line = 'pid: %5d ' % (test_event['pid']) + name
- else:
- line = name
- self.job_panel.set_line(worker_index, line)
- self.main_window.refresh()
- elif event == 'test_result':
- status = test_event['status']
- self.status_panel.increment_status(status)
- if 'pid' in test_event:
- line = 'pid: %5d ' % (test_event['pid'])
- else:
- line = ''
- self.job_panel.set_line(worker_index, line)
- # if status != 'success' and status != 'skip' and status != 'expect_failure':
- name = test_event['test_class'] + '.' + test_event['test_name']
- time = test_event['event_time'] - self.job_tests[worker_index]['event_time']
- self.fail_panel.append_line('%s (%6.2f sec) %s' % (self.status_to_short_str(status), time, name))
- self.main_window.refresh()
- self.job_tests[worker_index] = ''
- elif event == 'job_begin':
- self.jobs[worker_index] = test_event
- if 'pid' in test_event:
- line = 'pid: %5d ' % (test_event['pid'])
- else:
- line = ''
- self.job_panel.set_line(worker_index, line)
- elif event == 'job_end':
- self.jobs[worker_index] = ''
- self.job_panel.set_line(worker_index, '')
- elif event == 'initialize':
- self.initialize_event = test_event
- num_jobs = test_event['worker_count']
- job_frame = self.main_window.get_contained_rect(height=num_jobs+2)
- fail_frame = self.main_window.get_contained_rect(top_inset=num_jobs+2, bottom_inset=1)
- status_frame = self.main_window.get_contained_rect(height=1, top_inset=self.main_window.get_size().h-1)
- self.job_panel = lldbcurses.BoxedPanel(job_frame, "Jobs")
- self.fail_panel = lldbcurses.BoxedPanel(fail_frame, "Completed Tests")
- self.status_panel = lldbcurses.StatusPanel(status_frame)
- self.status_panel.add_status_item(name="success", title="Success", format="%u", width=20, value=0, update=False)
- self.status_panel.add_status_item(name="failure", title="Failure", format="%u", width=20, value=0, update=False)
- self.status_panel.add_status_item(name="error", title="Error", format="%u", width=20, value=0, update=False)
- self.status_panel.add_status_item(name="skip", title="Skipped", format="%u", width=20, value=0, update=True)
- self.status_panel.add_status_item(name="expected_failure", title="Expected Failure", format="%u", width=30, value=0, update=False)
- self.status_panel.add_status_item(name="unexpected_success", title="Unexpected Success", format="%u", width=30, value=0, update=False)
- self.main_window.refresh()
- elif event == 'terminate':
- lldbcurses.terminate_curses()
- self.using_terminal = False
-
class DumpFormatter(ResultsFormatter):
"""Formats events to the file as their raw python dictionary format."""
OpenPOWER on IntegriCloud