Add support for sequences of fully automated tests.

Paired with CL: http://codereview.chromium.org/2322004

Review URL: http://codereview.chromium.org/2367001
This commit is contained in:
Tammo Spalink 2010-06-07 16:37:55 +08:00
parent 7e12aadbbd
commit 8d43b4bf8c

View File

@ -35,12 +35,16 @@ import time
def XXX_log(s):
print >> sys.stderr, '--- XXX : ' + s
_ACTIVE = 'ACTIVE'
_PASSED = 'PASS'
_FAILED = 'FAIL'
_UNTESTED = 'UNTESTED'
_LABEL_COLORS = {
'active': gtk.gdk.color_parse('light goldenrod'),
'passed': gtk.gdk.color_parse('pale green'),
'failed': gtk.gdk.color_parse('tomato'),
'untested': gtk.gdk.color_parse('dark slate grey')}
_ACTIVE: gtk.gdk.color_parse('light goldenrod'),
_PASSED: gtk.gdk.color_parse('pale green'),
_FAILED: gtk.gdk.color_parse('tomato'),
_UNTESTED: gtk.gdk.color_parse('dark slate grey')}
_LABEL_EN_SIZE = (160, 35)
_LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
@ -50,10 +54,13 @@ _LABEL_T_SIZE = (30, 35)
_LABEL_T_FONT = pango.FontDescription('courier new italic ultra-condensed 10')
_LABEL_UNTESTED_FG = gtk.gdk.color_parse('grey40')
_LABEL_TROUGH_COLOR = gtk.gdk.color_parse('grey20')
_LABEL_STATUS_SIZE = (140, 30)
_LABEL_STATUS_FONT = pango.FontDescription(
'courier new bold extra-condensed 16')
_SEP_COLOR = gtk.gdk.color_parse('grey50')
_BLACK = gtk.gdk.color_parse('black')
_LIGHT_GREEN = gtk.gdk.color_parse('light green')
_OTHER_LABEL_FONT = pango.FontDescription('courier new condensed 20')
class console_proc:
'''Display a progress log. Implemented by launching an borderless
@ -87,6 +94,11 @@ def control_send(x):
print repr(x)
sys.stdout.flush()
def control_send_target_test_update(test):
XXX_log('ui send_target_test_update %s.%s_%s' %
(test.formal_name, test.tag_prefix, test.count))
control_send((test.formal_name, test.tag_prefix, test.count))
# Capture keyboard events here for debugging -- under normal
# circumstances, all keyboard events should be captured by executing
@ -97,85 +109,142 @@ def handle_key_release_event(_, event):
return True
def update_label_status(test, status):
if status != 'untested':
test.label_box.modify_fg(gtk.STATE_NORMAL, _BLACK)
for label in test.label_list:
label.modify_fg(gtk.STATE_NORMAL, _BLACK)
test.label_box.modify_bg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
test.label_box.queue_draw()
class test_label_box(gtk.EventBox):
def __init__(self, test):
gtk.EventBox.__init__(self)
label_en = gtk.Label(test.label_en)
label_en.set_size_request(*_LABEL_EN_SIZE)
label_en.modify_font(_LABEL_EN_FONT)
label_en.set_alignment(0.8, 0.5)
label_en.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
label_zw = gtk.Label(test.label_zw)
label_zw.set_size_request(*_LABEL_ZW_SIZE)
label_zw.modify_font(_LABEL_ZW_FONT)
label_zw.set_alignment(0.2, 0.5)
label_zw.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
label_t = gtk.Label('C-' + test.trigger)
label_t.set_size_request(*_LABEL_T_SIZE)
label_t.modify_font(_LABEL_T_FONT)
label_t.set_alignment(0.5, 0.5)
label_t.modify_fg(gtk.STATE_NORMAL, _BLACK)
hbox = gtk.HBox()
hbox.pack_start(label_en, False, False)
hbox.pack_start(label_zw, False, False)
hbox.pack_start(label_t, False, False)
self.add(hbox)
self.label_list = [label_en, label_zw]
def update_status(self, status):
if status != _UNTESTED:
self.modify_fg(gtk.STATE_NORMAL, _BLACK)
for label in self.label_list:
label.modify_fg(gtk.STATE_NORMAL, _BLACK)
self.modify_bg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
self.queue_draw()
class subtest_label_box(gtk.EventBox):
def __init__(self, test):
gtk.EventBox.__init__(self)
self.modify_bg(gtk.STATE_NORMAL, _BLACK)
label_status = gtk.Label(_UNTESTED)
label_status.set_size_request(*_LABEL_STATUS_SIZE)
label_status.set_alignment(0, 0.5)
label_status.modify_font(_LABEL_STATUS_FONT)
label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
label_en = gtk.Label(test.label_en)
label_en.set_alignment(1, 0.5)
label_en.modify_font(_LABEL_EN_FONT)
label_en.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
label_zw = gtk.Label(test.label_zw)
label_zw.set_alignment(1, 0.5)
label_zw.modify_font(_LABEL_ZW_FONT)
label_zw.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
label_sep = gtk.Label(' : ')
label_sep.set_alignment(0.5, 0.5)
label_sep.modify_font(_LABEL_EN_FONT)
label_sep.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
hbox = gtk.HBox()
hbox.pack_end(label_status, False, False)
hbox.pack_end(label_sep, False, False)
hbox.pack_end(label_zw, False, False)
hbox.pack_end(label_en, False, False)
self.add(hbox)
self.label_status = label_status
def update_status(self, status):
if status != _UNTESTED:
self.label_status.set_text(status)
self.label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
self.queue_draw()
class status_map():
def __init__(self):
self.status_dict = {}
def index(self, formal_name, tag_prefix):
return '%s.%s' % (formal_name, tag_prefix)
def lookup(self, formal_name, tag_prefix):
return self.status_dict.setdefault(
self.index(formal_name, tag_prefix),
(_UNTESTED, 0))
def update(self, formal_name, tag_prefix, status, count):
_, existing_count = self.lookup(formal_name, tag_prefix)
if count > existing_count:
index = self.index(formal_name, tag_prefix)
self.status_dict[index] = (status, count)
def get_subtest_status(self, test):
map(self.set_test_status, test.automated_seq)
sub_status_set = set(st.status for st in test.automated_seq)
min_count = min([st.count for st in test.automated_seq])
max_count = max([st.count for st in test.automated_seq])
if len(sub_status_set) == 1:
return (sub_status_set.pop(), max_count)
if test.count > min_count:
return (_ACTIVE, max_count)
return (_FAILED, max_count)
def set_test_status(self, test):
status, count = (
test.automated_seq
and self.get_subtest_status(test)
or self.lookup(test.formal_name, test.tag_prefix))
status = test.count > count and _ACTIVE or status
max_count = max(test.count, count)
if test.status != status or test.count != max_count:
XXX_log('status change for %s : %s/%s -> %s/%s' %
(self.index(test.formal_name, test.tag_prefix),
test.count, test.status, max_count, status))
test.status = status
test.count = max_count
test.label_box.update_status(status)
def refresh_test_status(status_file_path, test_list):
result_dict = {}
smap = status_map()
with open(status_file_path) as file:
for line in file:
columns = line.split('\t')
if len(columns) >= 8 and not columns[0] and not columns[1]:
result_state = columns[2]
full_name = columns[3]
result_dict[full_name] = result_state
for test in test_list:
full_name = '%s.%d' % (test.formal_name, test.count)
result_state = result_dict.get(full_name, None)
if result_state is None:
status = 'untested'
elif result_state == 'GOOD':
status = 'passed'
else:
status = 'failed'
if test.status != status:
XXX_log('status change for %s : %s -> %s' %
(test.label_en, test.status, status))
test.status = status
update_label_status(test, status)
status = columns[2] == 'GOOD' and _PASSED or _FAILED
formal_name, _, tag = columns[3].rpartition('.')
tag_prefix, _, count = tag.rpartition('_')
count = int(count)
smap.update(formal_name, tag_prefix, status, count)
map(smap.set_test_status, test_list)
def select_active_test(test_list, remaining_tests_queue,
test_counters, trigger):
active_test = None
if trigger is not None:
trigger_dict = dict((test.trigger, test) for test in test_list)
active_test = trigger_dict.get(trigger, None)
if active_test in remaining_tests_queue:
remaining_tests_queue.remove(active_test)
if active_test is None:
active_test = remaining_tests_queue.pop()
count = test_counters[active_test.formal_name]
count += 1
active_test.count = count
test_counters[active_test.formal_name] = count
update_label_status(active_test, 'active')
XXX_log('select_active_test %s.%d' %
(active_test.formal_name, active_test.count))
return (active_test.label_en, active_test.count)
def make_test_label(test):
label_en = gtk.Label(test.label_en)
label_en.set_size_request(*_LABEL_EN_SIZE)
label_en.modify_font(_LABEL_EN_FONT)
label_en.set_alignment(0.8, 0.5)
label_en.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
label_zw = gtk.Label(test.label_zw)
label_zw.set_size_request(*_LABEL_ZW_SIZE)
label_zw.modify_font(_LABEL_ZW_FONT)
label_zw.set_alignment(0.2, 0.5)
label_zw.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
label_t = gtk.Label('C-' + test.trigger)
label_t.set_size_request(*_LABEL_T_SIZE)
label_t.modify_font(_LABEL_T_FONT)
label_t.set_alignment(0.5, 0.5)
label_t.modify_fg(gtk.STATE_NORMAL, _BLACK)
hbox = gtk.HBox()
hbox.pack_start(label_en, False, False)
hbox.pack_start(label_zw, False, False)
hbox.pack_start(label_t, False, False)
label_box = gtk.EventBox()
label_box.add(hbox)
test.label_box = label_box
test.label_list = [label_en, label_zw]
return label_box
def set_active_test(test):
test.count += 1
test.label_box.update_status(_ACTIVE)
control_send_target_test_update(test)
def make_hsep(width=1):
@ -192,19 +261,23 @@ def make_vsep(width=1):
return frame
def make_test_widget_box():
def make_notest_label():
label = gtk.Label('no active test')
font = pango.FontDescription('courier new condensed 20')
label.modify_font(font)
label.modify_font(_OTHER_LABEL_FONT)
label.set_alignment(0.5, 0.5)
label.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
box = gtk.EventBox()
box.modify_bg(gtk.STATE_NORMAL, _BLACK)
box.add(label)
align = gtk.Alignment(xalign=0.5, yalign=0.5)
align.set_size_request(-1, -1)
align.add(box)
return align
return box
def make_automated_seq_widget(as_test):
vbox = gtk.VBox()
vbox.set_spacing(0)
map(lambda st: vbox.pack_start(st.label_box, False, False),
as_test.automated_seq)
return vbox
def main():
@ -227,7 +300,11 @@ def main():
console_box.set_size_request(-1, 180)
console_box.modify_bg(gtk.STATE_NORMAL, _BLACK)
test_widget_box = make_test_widget_box()
notest_label = make_notest_label()
test_widget_box = gtk.Alignment(xalign=0.5, yalign=0.5)
test_widget_box.set_size_request(-1, -1)
test_widget_box.add(notest_label)
lhs_box = gtk.VBox()
lhs_box.pack_end(console_box, False, False)
@ -252,8 +329,15 @@ def main():
for test in test_list:
test.status = None
label = make_test_label(test)
label_trough.pack_start(label, False, False)
test.count = 0
test.tag_prefix = test.trigger
test.label_box = test_label_box(test)
for subtest in test.automated_seq:
subtest.status = None
subtest.count = 0
subtest.tag_prefix = test.formal_name
subtest.label_box = subtest_label_box(subtest)
label_trough.pack_start(test.label_box, False, False)
label_trough.pack_start(make_hsep(), False, False)
window.add(base_box)
@ -265,15 +349,15 @@ def main():
XXX_log('test_widget_size = %s' % repr(test_widget_size))
control_send(test_widget_size)
# Use a common datastructure for counters to allow multiple tests
# to share the same formal name.
test_counters = dict((test.formal_name, 0) for test in test_list)
for test in test_list:
test.count = 0
trigger_dict = dict((test.trigger, test) for test in test_list)
refresh_test_status(status_file_path, test_list)
remaining_tests_queue = [x for x in reversed(test_list)
if test.status != 'passed']
if x.status == _UNTESTED]
XXX_log('remaining_tests_queue = %s' %
repr([x.label_en for x in remaining_tests_queue]))
active_test = None
gobject.io_add_watch(sys.stdin, gobject.IO_IN, stdin_callback)
@ -294,15 +378,44 @@ def main():
# (possibly None) to indicate how the next test should be selected.
while remaining_tests_queue:
trigger = control_recv()
XXX_log('ui received trigger (%s)' % trigger)
active_test_name, count = select_active_test(
test_list, remaining_tests_queue,
test_counters, trigger)
control_send((active_test_name, count))
gtk.main()
refresh_test_status(status_file_path, test_list)
command, arg = control_recv()
XXX_log('ui received command %s(%s)' % (command, arg))
if command == 'switch_to':
active_test = trigger_dict.get(arg, None)
if active_test in remaining_tests_queue:
remaining_tests_queue.remove(active_test)
set_active_test(active_test)
elif command == 'next_test':
active_test = remaining_tests_queue.pop()
set_active_test(active_test)
else:
XXX_log('ui command unknown, exiting...')
break
if active_test.automated_seq:
XXX_log('ui starting automated_seq')
subtest_queue = [x for x in reversed(active_test.automated_seq)]
test_widget_box.remove(notest_label)
as_widget = make_automated_seq_widget(active_test)
test_widget_box.add(as_widget)
window.show_all()
command = None
while command != 'quit_automated_seq':
active_subtest = subtest_queue.pop()
active_subtest.label_box.update_status(_ACTIVE)
gtk.main()
command = control_recv()
XXX_log('ui automated_seq step (%s)' % command)
refresh_test_status(status_file_path, test_list)
test_widget_box.queue_draw()
test_widget_box.remove(as_widget)
test_widget_box.add(notest_label)
window.show_all()
XXX_log('ui exiting automated_seq')
else:
gtk.main()
refresh_test_status(status_file_path, test_list)
# Tell the control process we are done.
control_send((None, 0))
XXX_log('exiting ui')