Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(406)

Unified Diff: src/scripts/mod_for_factory_scripts/factory_ui

Issue 1937002: Launch X server from upstart. New factory UI. Misc script tweaks. (Closed) Base URL: ssh://git@chromiumos-git/chromeos
Patch Set: response to feedback Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/scripts/mod_for_factory_scripts/factory_startx.sh ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/scripts/mod_for_factory_scripts/factory_ui
diff --git a/src/scripts/mod_for_factory_scripts/factory_ui b/src/scripts/mod_for_factory_scripts/factory_ui
new file mode 100644
index 0000000000000000000000000000000000000000..8b40199f82b8336d3c048ef4ab95904019983d89
--- /dev/null
+++ b/src/scripts/mod_for_factory_scripts/factory_ui
@@ -0,0 +1,318 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# DESCRIPTION :
+#
+# This UI is intended to be used by the factory autotest suite to
+# provide factory operators feedback on test status and control over
+# execution order.
+#
+# In short, the UI is composed of a 'console' panel on the bottom of
+# the screen which displays the autotest log, and there is also a
+# 'test list' panel on the right hand side of the screen. The
+# majority of the screen is dedicated to tests, which are executed in
+# seperate processes, but instructed to display their own UIs in this
+# dedicated area whenever possible. Tests in the test list are
+# executed in order by default, but can be activated on demand via
+# associated keyboard shortcuts (triggers). As tests are run, their
+# status is color-indicated to the operator -- greyed out means
+# untested, yellow means active, green passed and red failed.
+
+
+import gobject
+import gtk
+import os
+import pango
+import subprocess
+import sys
+import time
+
+
+def XXX_log(s):
+ print >> sys.stderr, '--- XXX : ' + s
+
+
+_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')}
+
+_LABEL_EN_SIZE = (160, 35)
+_LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
+_LABEL_ZW_SIZE = (70, 35)
+_LABEL_ZW_FONT = pango.FontDescription('normal 12')
+_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')
+_SEP_COLOR = gtk.gdk.color_parse('grey50')
+_BLACK = gtk.gdk.color_parse('black')
+_LIGHT_GREEN = gtk.gdk.color_parse('light green')
+
+
+class console_proc:
+ '''Display a progress log. Implemented by launching an borderless
+ xterm at a strategic location, and running tail against the log.'''
+
+ def __init__(self, allocation, log_file_path):
+ xterm_coords = '135x14+%d+%d' % (allocation.x, allocation.y)
+ XXX_log('xterm_coords = %s' % xterm_coords)
+ xterm_cmd = ('xterm --geometry %s -bw 0 -e ' % xterm_coords +
+ 'tail -f %s' % log_file_path)
+ self._proc = subprocess.Popen(xterm_cmd.split())
+
+ def __del__(self):
+ XXX_log('console_proc __del__')
+ self._proc.kill()
+
+
+# Routines to communicate with the autotest control file, using python
+# expressions. The stdin_callback assures notification for any new
+# messages.
+
+def stdin_callback(s, c):
+ XXX_log('stdin_callback, quitting gtk main')
+ gtk.main_quit()
+ return True
+
+def control_recv():
+ return eval(sys.stdin.readline().rstrip())
+
+def control_send(x):
+ print repr(x)
+ sys.stdout.flush()
+
+
+# Capture keyboard events here for debugging -- under normal
+# circumstances, all keyboard events should be captured by executing
+# tests, and hence this should not be called.
+
+def handle_key_release_event(_, event):
+ XXX_log('base ui key event (%s)' % event.keyval)
+ 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()
+
+
+def refresh_test_status(status_file_path, test_list):
+ result_dict = {}
+ 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)
+
+
+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 make_hsep(width=1):
+ frame = gtk.EventBox()
+ frame.set_size_request(-1, width)
+ frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
+ return frame
+
+
+def make_vsep(width=1):
+ frame = gtk.EventBox()
+ frame.set_size_request(width, -1)
+ frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
+ return frame
+
+
+def make_test_widget_box():
+ label = gtk.Label('no active test')
+ font = pango.FontDescription('courier new condensed 20')
+ label.modify_font(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
+
+
+def main():
+ window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ window.connect('destroy', lambda _: gtk.main_quit())
+ window.modify_bg(gtk.STATE_NORMAL, _BLACK)
+
+ screen = window.get_screen()
+ screen_size = (screen.get_width(), screen.get_height())
+ window.set_size_request(*screen_size)
+
+ label_trough = gtk.VBox()
+ label_trough.set_spacing(0)
+
+ rhs_box = gtk.EventBox()
+ rhs_box.modify_bg(gtk.STATE_NORMAL, _LABEL_TROUGH_COLOR)
+ rhs_box.add(label_trough)
+
+ console_box = gtk.EventBox()
+ console_box.set_size_request(-1, 180)
+ console_box.modify_bg(gtk.STATE_NORMAL, _BLACK)
+
+ test_widget_box = make_test_widget_box()
+
+ lhs_box = gtk.VBox()
+ lhs_box.pack_end(console_box, False, False)
+ lhs_box.pack_start(test_widget_box)
+ lhs_box.pack_start(make_hsep(3), False, False)
+
+ base_box = gtk.HBox()
+ base_box.pack_end(rhs_box, False, False)
+ base_box.pack_end(make_vsep(3), False, False)
+ base_box.pack_start(lhs_box)
+
+ window.connect('key-release-event', handle_key_release_event)
+ window.add_events(gtk.gdk.KEY_RELEASE_MASK)
+
+ # On startup, get general configuration data from the autotest
+ # control program, specifically the list of tests to run (in
+ # order) and some filenames.
+ XXX_log('pulling control info')
+ test_list = control_recv()
+ status_file_path = control_recv()
+ log_file_path = control_recv()
+
+ for test in test_list:
+ test.status = None
+ label = make_test_label(test)
+ label_trough.pack_start(label, False, False)
+ label_trough.pack_start(make_hsep(), False, False)
+
+ window.add(base_box)
+ window.show_all()
+
+ test_widget_allocation = test_widget_box.get_allocation()
+ test_widget_size = (test_widget_allocation.width,
+ test_widget_allocation.height)
+ 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
+
+ refresh_test_status(status_file_path, test_list)
+ remaining_tests_queue = [x for x in reversed(test_list)
+ if test.status != 'passed']
+
+ gobject.io_add_watch(sys.stdin, gobject.IO_IN, stdin_callback)
+
+ console = console_proc(console_box.get_allocation(), log_file_path)
+
+ XXX_log('finished ui setup')
+
+ # Test selection is driven either by triggers or by the
+ # remaining_tests_queue. If a trigger was seen, explicitly run
+ # the corresponding test. Otherwise choose the next test from the
+ # queue. Tests are removed from the queue as they are run,
+ # regarless of the outcome. Tests that are interrupted by trigger
+ # are treated as having failed.
+ #
+ # Iterations in the main loop here are driven by data availability
+ # on stdin, which is used to communicate with the autotest control
+ # program. On each step through the loop, a trigger is received
+ # (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)
+
+ control_send((None, 0))
+
+ XXX_log('exiting ui')
+
+if __name__ == '__main__':
+
+ # In global scope, get the test_data class description from the
+ # control program -- this allows a convenient single point of
+ # definition for this class.
+ test_data_class_def = control_recv()
+ exec(test_data_class_def)
+
+ main()
« no previous file with comments | « src/scripts/mod_for_factory_scripts/factory_startx.sh ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698