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

Side by Side Diff: mod_for_factory_scripts/factory_ui

Issue 2825014: Move factory UI files to autotest repo. (Closed) Base URL: ssh://gitrw.chromium.org/crosutils.git
Patch Set: move startx to deps Created 10 years, 6 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 unified diff | Download patch
« no previous file with comments | « mod_for_factory_scripts/factory_startx.sh ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7
8 # DESCRIPTION :
9 #
10 # This UI is intended to be used by the factory autotest suite to
11 # provide factory operators feedback on test status and control over
12 # execution order.
13 #
14 # In short, the UI is composed of a 'console' panel on the bottom of
15 # the screen which displays the autotest log, and there is also a
16 # 'test list' panel on the right hand side of the screen. The
17 # majority of the screen is dedicated to tests, which are executed in
18 # seperate processes, but instructed to display their own UIs in this
19 # dedicated area whenever possible. Tests in the test list are
20 # executed in order by default, but can be activated on demand via
21 # associated keyboard shortcuts (triggers). As tests are run, their
22 # status is color-indicated to the operator -- greyed out means
23 # untested, yellow means active, green passed and red failed.
24
25
26 import gobject
27 import gtk
28 import os
29 import pango
30 import subprocess
31 import sys
32 import time
33
34
35 def XXX_log(s):
36 print >> sys.stderr, '--- XXX : ' + s
37
38 _ACTIVE = 'ACTIVE'
39 _PASSED = 'PASS'
40 _FAILED = 'FAIL'
41 _UNTESTED = 'UNTESTED'
42
43 _LABEL_COLORS = {
44 _ACTIVE: gtk.gdk.color_parse('light goldenrod'),
45 _PASSED: gtk.gdk.color_parse('pale green'),
46 _FAILED: gtk.gdk.color_parse('tomato'),
47 _UNTESTED: gtk.gdk.color_parse('dark slate grey')}
48
49 _LABEL_EN_SIZE = (160, 35)
50 _LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
51 _LABEL_ZW_SIZE = (70, 35)
52 _LABEL_ZW_FONT = pango.FontDescription('normal 12')
53 _LABEL_T_SIZE = (30, 35)
54 _LABEL_T_FONT = pango.FontDescription('courier new italic ultra-condensed 10')
55 _LABEL_UNTESTED_FG = gtk.gdk.color_parse('grey40')
56 _LABEL_TROUGH_COLOR = gtk.gdk.color_parse('grey20')
57 _LABEL_STATUS_SIZE = (140, 30)
58 _LABEL_STATUS_FONT = pango.FontDescription(
59 'courier new bold extra-condensed 16')
60 _SEP_COLOR = gtk.gdk.color_parse('grey50')
61 _BLACK = gtk.gdk.color_parse('black')
62 _LIGHT_GREEN = gtk.gdk.color_parse('light green')
63 _OTHER_LABEL_FONT = pango.FontDescription('courier new condensed 20')
64
65 class console_proc:
66 '''Display a progress log. Implemented by launching an borderless
67 xterm at a strategic location, and running tail against the log.'''
68
69 def __init__(self, allocation, log_file_path):
70 xterm_coords = '135x14+%d+%d' % (allocation.x, allocation.y)
71 XXX_log('xterm_coords = %s' % xterm_coords)
72 xterm_cmd = ('xterm --geometry %s -bw 0 -e ' % xterm_coords +
73 'tail -f %s' % log_file_path)
74 self._proc = subprocess.Popen(xterm_cmd.split())
75
76 def __del__(self):
77 XXX_log('console_proc __del__')
78 self._proc.kill()
79
80
81 # Routines to communicate with the autotest control file, using python
82 # expressions. The stdin_callback assures notification for any new
83 # messages.
84
85 def stdin_callback(s, c):
86 XXX_log('stdin_callback, quitting gtk main')
87 gtk.main_quit()
88 return True
89
90 def control_recv():
91 return eval(sys.stdin.readline().rstrip())
92
93 def control_send(x):
94 print repr(x)
95 sys.stdout.flush()
96
97 def control_send_target_test_update(test):
98 XXX_log('ui send_target_test_update %s.%s_%s' %
99 (test.formal_name, test.tag_prefix, test.count))
100 control_send((test.formal_name, test.tag_prefix, test.count))
101
102
103 # Capture keyboard events here for debugging -- under normal
104 # circumstances, all keyboard events should be captured by executing
105 # tests, and hence this should not be called.
106
107 def handle_key_release_event(_, event):
108 XXX_log('base ui key event (%s)' % event.keyval)
109 return True
110
111
112 class test_label_box(gtk.EventBox):
113
114 def __init__(self, test):
115 gtk.EventBox.__init__(self)
116 label_en = gtk.Label(test.label_en)
117 label_en.set_size_request(*_LABEL_EN_SIZE)
118 label_en.modify_font(_LABEL_EN_FONT)
119 label_en.set_alignment(0.8, 0.5)
120 label_en.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
121 label_zw = gtk.Label(test.label_zw)
122 label_zw.set_size_request(*_LABEL_ZW_SIZE)
123 label_zw.modify_font(_LABEL_ZW_FONT)
124 label_zw.set_alignment(0.2, 0.5)
125 label_zw.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
126 label_t = gtk.Label('C-' + test.trigger)
127 label_t.set_size_request(*_LABEL_T_SIZE)
128 label_t.modify_font(_LABEL_T_FONT)
129 label_t.set_alignment(0.5, 0.5)
130 label_t.modify_fg(gtk.STATE_NORMAL, _BLACK)
131 hbox = gtk.HBox()
132 hbox.pack_start(label_en, False, False)
133 hbox.pack_start(label_zw, False, False)
134 hbox.pack_start(label_t, False, False)
135 self.add(hbox)
136 self.label_list = [label_en, label_zw]
137
138 def update_status(self, status):
139 if status != _UNTESTED:
140 self.modify_fg(gtk.STATE_NORMAL, _BLACK)
141 for label in self.label_list:
142 label.modify_fg(gtk.STATE_NORMAL, _BLACK)
143 self.modify_bg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
144 self.queue_draw()
145
146
147 class subtest_label_box(gtk.EventBox):
148
149 def __init__(self, test):
150 gtk.EventBox.__init__(self)
151 self.modify_bg(gtk.STATE_NORMAL, _BLACK)
152 label_status = gtk.Label(_UNTESTED)
153 label_status.set_size_request(*_LABEL_STATUS_SIZE)
154 label_status.set_alignment(0, 0.5)
155 label_status.modify_font(_LABEL_STATUS_FONT)
156 label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
157 label_en = gtk.Label(test.label_en)
158 label_en.set_alignment(1, 0.5)
159 label_en.modify_font(_LABEL_EN_FONT)
160 label_en.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
161 label_zw = gtk.Label(test.label_zw)
162 label_zw.set_alignment(1, 0.5)
163 label_zw.modify_font(_LABEL_ZW_FONT)
164 label_zw.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
165 label_sep = gtk.Label(' : ')
166 label_sep.set_alignment(0.5, 0.5)
167 label_sep.modify_font(_LABEL_EN_FONT)
168 label_sep.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
169 hbox = gtk.HBox()
170 hbox.pack_end(label_status, False, False)
171 hbox.pack_end(label_sep, False, False)
172 hbox.pack_end(label_zw, False, False)
173 hbox.pack_end(label_en, False, False)
174 self.add(hbox)
175 self.label_status = label_status
176
177 def update_status(self, status):
178 if status != _UNTESTED:
179 self.label_status.set_text(status)
180 self.label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
181 self.queue_draw()
182
183
184 class status_map():
185
186 def __init__(self):
187 self.status_dict = {}
188
189 def index(self, formal_name, tag_prefix):
190 return '%s.%s' % (formal_name, tag_prefix)
191
192 def lookup(self, formal_name, tag_prefix):
193 return self.status_dict.setdefault(
194 self.index(formal_name, tag_prefix),
195 (_UNTESTED, 0))
196
197 def update(self, formal_name, tag_prefix, status, count):
198 _, existing_count = self.lookup(formal_name, tag_prefix)
199 if count > existing_count:
200 index = self.index(formal_name, tag_prefix)
201 self.status_dict[index] = (status, count)
202
203 def get_subtest_status(self, test):
204 map(self.set_test_status, test.automated_seq)
205 sub_status_set = set(st.status for st in test.automated_seq)
206 min_count = min([st.count for st in test.automated_seq])
207 max_count = max([st.count for st in test.automated_seq])
208 if len(sub_status_set) == 1:
209 return (sub_status_set.pop(), max_count)
210 if test.count > min_count:
211 return (_ACTIVE, max_count)
212 return (_FAILED, max_count)
213
214 def set_test_status(self, test):
215 status, count = (
216 test.automated_seq
217 and self.get_subtest_status(test)
218 or self.lookup(test.formal_name, test.tag_prefix))
219 status = test.count > count and _ACTIVE or status
220 max_count = max(test.count, count)
221 if test.status != status or test.count != max_count:
222 XXX_log('status change for %s : %s/%s -> %s/%s' %
223 (self.index(test.formal_name, test.tag_prefix),
224 test.count, test.status, max_count, status))
225 test.status = status
226 test.count = max_count
227 test.label_box.update_status(status)
228
229
230 def refresh_test_status(status_file_path, test_list):
231 smap = status_map()
232 with open(status_file_path) as file:
233 for line in file:
234 columns = line.split('\t')
235 if len(columns) >= 8 and not columns[0] and not columns[1]:
236 status = columns[2] == 'GOOD' and _PASSED or _FAILED
237 formal_name, _, tag = columns[3].rpartition('.')
238 tag_prefix, _, count = tag.rpartition('_')
239 count = int(count)
240 smap.update(formal_name, tag_prefix, status, count)
241 map(smap.set_test_status, test_list)
242
243
244 def set_active_test(test):
245 test.count += 1
246 test.label_box.update_status(_ACTIVE)
247 control_send_target_test_update(test)
248
249
250 def make_hsep(width=1):
251 frame = gtk.EventBox()
252 frame.set_size_request(-1, width)
253 frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
254 return frame
255
256
257 def make_vsep(width=1):
258 frame = gtk.EventBox()
259 frame.set_size_request(width, -1)
260 frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
261 return frame
262
263
264 def make_notest_label():
265 label = gtk.Label('no active test')
266 label.modify_font(_OTHER_LABEL_FONT)
267 label.set_alignment(0.5, 0.5)
268 label.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
269 box = gtk.EventBox()
270 box.modify_bg(gtk.STATE_NORMAL, _BLACK)
271 box.add(label)
272 return box
273
274
275 def make_automated_seq_widget(as_test):
276 vbox = gtk.VBox()
277 vbox.set_spacing(0)
278 map(lambda st: vbox.pack_start(st.label_box, False, False),
279 as_test.automated_seq)
280 return vbox
281
282
283 def main():
284 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
285 window.connect('destroy', lambda _: gtk.main_quit())
286 window.modify_bg(gtk.STATE_NORMAL, _BLACK)
287
288 screen = window.get_screen()
289 screen_size = (screen.get_width(), screen.get_height())
290 window.set_size_request(*screen_size)
291
292 label_trough = gtk.VBox()
293 label_trough.set_spacing(0)
294
295 rhs_box = gtk.EventBox()
296 rhs_box.modify_bg(gtk.STATE_NORMAL, _LABEL_TROUGH_COLOR)
297 rhs_box.add(label_trough)
298
299 console_box = gtk.EventBox()
300 console_box.set_size_request(-1, 180)
301 console_box.modify_bg(gtk.STATE_NORMAL, _BLACK)
302
303 notest_label = make_notest_label()
304
305 test_widget_box = gtk.Alignment(xalign=0.5, yalign=0.5)
306 test_widget_box.set_size_request(-1, -1)
307 test_widget_box.add(notest_label)
308
309 lhs_box = gtk.VBox()
310 lhs_box.pack_end(console_box, False, False)
311 lhs_box.pack_start(test_widget_box)
312 lhs_box.pack_start(make_hsep(3), False, False)
313
314 base_box = gtk.HBox()
315 base_box.pack_end(rhs_box, False, False)
316 base_box.pack_end(make_vsep(3), False, False)
317 base_box.pack_start(lhs_box)
318
319 window.connect('key-release-event', handle_key_release_event)
320 window.add_events(gtk.gdk.KEY_RELEASE_MASK)
321
322 # On startup, get general configuration data from the autotest
323 # control program, specifically the list of tests to run (in
324 # order) and some filenames.
325 XXX_log('pulling control info')
326 test_list = control_recv()
327 status_file_path = control_recv()
328 log_file_path = control_recv()
329
330 for test in test_list:
331 test.status = None
332 test.count = 0
333 test.tag_prefix = test.trigger
334 test.label_box = test_label_box(test)
335 for subtest in test.automated_seq:
336 subtest.status = None
337 subtest.count = 0
338 subtest.tag_prefix = test.formal_name
339 subtest.label_box = subtest_label_box(subtest)
340 label_trough.pack_start(test.label_box, False, False)
341 label_trough.pack_start(make_hsep(), False, False)
342
343 window.add(base_box)
344 window.show_all()
345
346 test_widget_allocation = test_widget_box.get_allocation()
347 test_widget_size = (test_widget_allocation.width,
348 test_widget_allocation.height)
349 XXX_log('test_widget_size = %s' % repr(test_widget_size))
350 control_send(test_widget_size)
351
352 trigger_dict = dict((test.trigger, test) for test in test_list)
353
354 refresh_test_status(status_file_path, test_list)
355 remaining_tests_queue = [x for x in reversed(test_list)
356 if x.status == _UNTESTED]
357 XXX_log('remaining_tests_queue = %s' %
358 repr([x.label_en for x in remaining_tests_queue]))
359
360 active_test = None
361
362 gobject.io_add_watch(sys.stdin, gobject.IO_IN, stdin_callback)
363
364 console = console_proc(console_box.get_allocation(), log_file_path)
365
366 XXX_log('finished ui setup')
367
368 # Test selection is driven either by triggers or by the
369 # remaining_tests_queue. If a trigger was seen, explicitly run
370 # the corresponding test. Otherwise choose the next test from the
371 # queue. Tests are removed from the queue as they are run,
372 # regarless of the outcome. Tests that are interrupted by trigger
373 # are treated as having failed.
374 #
375 # Iterations in the main loop here are driven by data availability
376 # on stdin, which is used to communicate with the autotest control
377 # program. On each step through the loop, a trigger is received
378 # (possibly None) to indicate how the next test should be selected.
379
380 while remaining_tests_queue:
381 command, arg = control_recv()
382 XXX_log('ui received command %s(%s)' % (command, arg))
383 if command == 'switch_to':
384 active_test = trigger_dict.get(arg, None)
385 if active_test in remaining_tests_queue:
386 remaining_tests_queue.remove(active_test)
387 set_active_test(active_test)
388 elif command == 'next_test':
389 active_test = remaining_tests_queue.pop()
390 set_active_test(active_test)
391 else:
392 XXX_log('ui command unknown, exiting...')
393 break
394 if active_test.automated_seq:
395 XXX_log('ui starting automated_seq')
396 subtest_queue = [x for x in reversed(active_test.automated_seq)]
397 test_widget_box.remove(notest_label)
398 as_widget = make_automated_seq_widget(active_test)
399 test_widget_box.add(as_widget)
400 window.show_all()
401 command = None
402 while command != 'quit_automated_seq':
403 active_subtest = subtest_queue.pop()
404 active_subtest.label_box.update_status(_ACTIVE)
405 gtk.main()
406 command = control_recv()
407 XXX_log('ui automated_seq step (%s)' % command)
408 refresh_test_status(status_file_path, test_list)
409 test_widget_box.queue_draw()
410 test_widget_box.remove(as_widget)
411 test_widget_box.add(notest_label)
412 window.show_all()
413 XXX_log('ui exiting automated_seq')
414 else:
415 gtk.main()
416 refresh_test_status(status_file_path, test_list)
417
418 # Tell the control process we are done.
419 control_send((None, 0))
420
421 XXX_log('exiting ui')
422
423 if __name__ == '__main__':
424
425 # In global scope, get the test_data class description from the
426 # control program -- this allows a convenient single point of
427 # definition for this class.
428 test_data_class_def = control_recv()
429 exec(test_data_class_def)
430
431 main()
OLDNEW
« no previous file with comments | « 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