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

Side by Side Diff: client/deps/factory/ui

Issue 2857012: Batched update of factory tests. (Closed) Base URL: ssh://gitrw.chromium.org/autotest.git
Patch Set: finished comment 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 | « client/deps/factory/startx.sh ('k') | client/site_tests/factory_Display/factory_Display.py » ('j') | 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, 'FACTORY: ' + s
37
38 _ACTIVE = 'ACTIVE'
39 _PASSED = 'PASS'
40 _FAILED = 'FAIL'
41 _UNTESTED = 'UNTESTED'
42
43 _STATUS_CODE_MAP = {
44 'START': _ACTIVE,
45 'GOOD': _PASSED,
46 'FAIL': _FAILED,
47 'ERROR': _FAILED}
48
49 _LABEL_COLORS = {
50 _ACTIVE: gtk.gdk.color_parse('light goldenrod'),
51 _PASSED: gtk.gdk.color_parse('pale green'),
52 _FAILED: gtk.gdk.color_parse('tomato'),
53 _UNTESTED: gtk.gdk.color_parse('dark slate grey')}
54
55 _LABEL_EN_SIZE = (160, 35)
56 _LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
57 _LABEL_ZW_SIZE = (70, 35)
58 _LABEL_ZW_FONT = pango.FontDescription('normal 12')
59 _LABEL_T_SIZE = (30, 35)
60 _LABEL_T_FONT = pango.FontDescription('courier new italic ultra-condensed 10')
61 _LABEL_UNTESTED_FG = gtk.gdk.color_parse('grey40')
62 _LABEL_TROUGH_COLOR = gtk.gdk.color_parse('grey20')
63 _LABEL_STATUS_SIZE = (140, 30)
64 _LABEL_STATUS_FONT = pango.FontDescription(
65 'courier new bold extra-condensed 16')
66 _SEP_COLOR = gtk.gdk.color_parse('grey50')
67 _BLACK = gtk.gdk.color_parse('black')
68 _LIGHT_GREEN = gtk.gdk.color_parse('light green')
69 _OTHER_LABEL_FONT = pango.FontDescription('courier new condensed 20')
70
71 class console_proc:
72 '''Display a progress log. Implemented by launching an borderless
73 xterm at a strategic location, and running tail against the log.'''
74
75 def __init__(self, allocation, log_file_path):
76 xterm_coords = '135x14+%d+%d' % (allocation.x, allocation.y)
77 XXX_log('xterm_coords = %s' % xterm_coords)
78 xterm_cmd = (('aterm --geometry %s -bw 0 -e bash -c ' %
79 xterm_coords).split() +
80 ['tail -f %s | grep FACTORY' % log_file_path])
81 XXX_log('xterm_cmd = %s' % xterm_cmd)
82 self._proc = subprocess.Popen(xterm_cmd)
83
84 def __del__(self):
85 XXX_log('console_proc __del__')
86 self._proc.kill()
87
88
89 # Routines to communicate with the autotest control file, using python
90 # expressions. The stdin_callback assures notification for any new
91 # messages.
92
93 def stdin_callback(s, c):
94 XXX_log('stdin_callback, quitting gtk main')
95 gtk.main_quit()
96 return True
97
98 def control_recv():
99 return eval(sys.stdin.readline().rstrip())
100
101 def control_send(x):
102 print repr(x)
103 sys.stdout.flush()
104
105 def control_send_target_test_update(test, count):
106 XXX_log('ui send_target_test_update %s.%s_%s' %
107 (test.formal_name, test.tag_prefix, count))
108 control_send((test.formal_name, test.tag_prefix, count))
109
110
111 # Capture keyboard events here for debugging -- under normal
112 # circumstances, all keyboard events should be captured by executing
113 # tests, and hence this should not be called.
114
115 def handle_key_release_event(_, event):
116 XXX_log('base ui key event (%s)' % event.keyval)
117 return True
118
119
120 class TestLabelBox(gtk.EventBox):
121
122 def __init__(self, test):
123 gtk.EventBox.__init__(self)
124 self.modify_bg(gtk.STATE_NORMAL, _LABEL_COLORS[_UNTESTED])
125 label_en = gtk.Label(test.label_en)
126 label_en.set_size_request(*_LABEL_EN_SIZE)
127 label_en.modify_font(_LABEL_EN_FONT)
128 label_en.set_alignment(0.5, 0.5)
129 label_en.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
130 label_zw = gtk.Label(test.label_zw)
131 label_zw.set_size_request(*_LABEL_ZW_SIZE)
132 label_zw.modify_font(_LABEL_ZW_FONT)
133 label_zw.set_alignment(0.5, 0.5)
134 label_zw.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
135 label_t = gtk.Label('C-' + test.trigger)
136 label_t.set_size_request(*_LABEL_T_SIZE)
137 label_t.modify_font(_LABEL_T_FONT)
138 label_t.set_alignment(0.5, 0.5)
139 label_t.modify_fg(gtk.STATE_NORMAL, _BLACK)
140 hbox = gtk.HBox()
141 hbox.pack_start(label_en, False, False)
142 hbox.pack_start(label_zw, False, False)
143 hbox.pack_start(label_t, False, False)
144 self.add(hbox)
145 self.label_list = [label_en, label_zw]
146
147 def update(self, status):
148 if status == _UNTESTED:
149 return
150 self.modify_fg(gtk.STATE_NORMAL, _BLACK)
151 for label in self.label_list:
152 label.modify_fg(gtk.STATE_NORMAL, _BLACK)
153 self.modify_bg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
154 self.queue_draw()
155
156
157 class SubTestLabelBox(gtk.EventBox):
158
159 def __init__(self, test):
160 gtk.EventBox.__init__(self)
161 self.modify_bg(gtk.STATE_NORMAL, _BLACK)
162 label_status = gtk.Label(_UNTESTED)
163 label_status.set_size_request(*_LABEL_STATUS_SIZE)
164 label_status.set_alignment(0, 0.5)
165 label_status.modify_font(_LABEL_STATUS_FONT)
166 label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_UNTESTED_FG)
167 label_en = gtk.Label(test.label_en)
168 label_en.set_alignment(1, 0.5)
169 label_en.modify_font(_LABEL_EN_FONT)
170 label_en.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
171 label_zw = gtk.Label(test.label_zw)
172 label_zw.set_alignment(1, 0.5)
173 label_zw.modify_font(_LABEL_ZW_FONT)
174 label_zw.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
175 label_sep = gtk.Label(' : ')
176 label_sep.set_alignment(0.5, 0.5)
177 label_sep.modify_font(_LABEL_EN_FONT)
178 label_sep.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
179 hbox = gtk.HBox()
180 hbox.pack_end(label_status, False, False)
181 hbox.pack_end(label_sep, False, False)
182 hbox.pack_end(label_zw, False, False)
183 hbox.pack_end(label_en, False, False)
184 self.add(hbox)
185 self.label_status = label_status
186
187 def update(self, status):
188 if status == _UNTESTED:
189 return
190 self.label_status.set_text(status)
191 self.label_status.modify_fg(gtk.STATE_NORMAL, _LABEL_COLORS[status])
192 self.queue_draw()
193
194
195 class StatusMap():
196
197 def __init__(self, status_file_path, test_list):
198 self._test_queue = [t for t in reversed(test_list)]
199 self._as_test_set = set(t for t in test_list if t.automated_seq)
200 self._status_dict = {}
201 for test in test_list:
202 test_index = self.index(test.formal_name, test.tag_prefix)
203 self._status_dict[test_index] = (test, _UNTESTED, 0, None)
204 for subtest in test.automated_seq:
205 st_index = self.index(subtest.formal_name, subtest.tag_prefix)
206 self._status_dict[st_index] = (subtest, _UNTESTED, 0, None)
207 self._status_file_path = status_file_path
208 self._status_file_pos = 0
209 self.read_new_data()
210 gobject.timeout_add(200, self.read_new_data)
211
212 def index(self, formal_name, tag_prefix):
213 return '%s.%s' % (formal_name, tag_prefix)
214
215 def next_untested(self):
216 remaining = [t for t in self._test_queue
217 if self.lookup_status(t) == _UNTESTED]
218 XXX_log('remaining untested = [%s]' %
219 ', '.join([self.index(t.formal_name, t.tag_prefix)
220 for t in remaining]))
221 if not remaining: return None
222 return remaining.pop()
223
224 def read_new_data(self):
225 with open(self._status_file_path) as file:
226 file.seek(self._status_file_pos)
227 for line in file:
228 cols = line.lstrip().split('\t') + ['']
229 code = cols[0]
230 test_id = cols[1]
231 if code not in _STATUS_CODE_MAP or test_id == '----':
232 continue
233 status = _STATUS_CODE_MAP[code]
234 XXX_log('reading code = %s, test_id = %s' % (code, test_id))
235 formal_name, _, tag = test_id.rpartition('.')
236 tag_prefix, _, count = tag.rpartition('_')
237 self.update(formal_name, tag_prefix, status, int(count))
238 self._status_file_pos = file.tell()
239 map(self.update_as_test, self._as_test_set)
240 return True
241
242 def update(self, formal_name, tag_prefix, status, count):
243 test_index = self.index(formal_name, tag_prefix)
244 if test_index not in self._status_dict:
245 XXX_log('ignoring status update (%s) for test %s' %
246 (status, test_index))
247 return
248 test, old_status, old_count, label = self._status_dict[test_index]
249 if count < old_count:
250 XXX_log('ERROR: count regression for %s (%d-%d)' %
251 (test_index, old_count, count))
252 if status != old_status:
253 XXX_log('status change for %s : %s/%s -> %s/%s' %
254 (test_index, old_status, old_count, status, count))
255 if label is not None:
256 label.update(status)
257 self._status_dict[test_index] = (test, status, count, label)
258
259 def update_as_test(self, test):
260 st_status_set = set(map(self.lookup_status, test.automated_seq))
261 max_count = max(map(self.lookup_count, test.automated_seq))
262 if len(st_status_set) == 1:
263 status = st_status_set.pop()
264 elif _ACTIVE in st_status_set:
265 status = _ACTIVE
266 else:
267 status = _FAILED
268 self.update(test.formal_name, test.tag_prefix, status, max_count)
269
270 def set_label(self, test, label):
271 test_index = self.index(test.formal_name, test.tag_prefix)
272 test, status, count, _ = self._status_dict[test_index]
273 label.update(status)
274 self._status_dict[test_index] = test, status, count, label
275
276 def lookup_status(self, test):
277 test_index = self.index(test.formal_name, test.tag_prefix)
278 return self._status_dict[test_index][1]
279
280 def lookup_count(self, test):
281 test_index = self.index(test.formal_name, test.tag_prefix)
282 return self._status_dict[test_index][2]
283
284 def lookup_label(self, test):
285 test_index = self.index(test.formal_name, test.tag_prefix)
286 return self._status_dict[test_index][3]
287
288
289 def make_hsep(width=1):
290 frame = gtk.EventBox()
291 frame.set_size_request(-1, width)
292 frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
293 return frame
294
295
296 def make_vsep(width=1):
297 frame = gtk.EventBox()
298 frame.set_size_request(width, -1)
299 frame.modify_bg(gtk.STATE_NORMAL, _SEP_COLOR)
300 return frame
301
302
303 def make_notest_label():
304 label = gtk.Label('no active test')
305 label.modify_font(_OTHER_LABEL_FONT)
306 label.set_alignment(0.5, 0.5)
307 label.modify_fg(gtk.STATE_NORMAL, _LIGHT_GREEN)
308 box = gtk.EventBox()
309 box.modify_bg(gtk.STATE_NORMAL, _BLACK)
310 box.add(label)
311 return box
312
313
314 def make_automated_seq_widget(as_test, status_map):
315 vbox = gtk.VBox()
316 vbox.set_spacing(0)
317 map(lambda st: vbox.pack_start(status_map.lookup_label(st), False, False),
318 as_test.automated_seq)
319 return vbox
320
321
322 def main():
323 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
324 window.connect('destroy', lambda _: gtk.main_quit())
325 window.modify_bg(gtk.STATE_NORMAL, _BLACK)
326
327 screen = window.get_screen()
328 screen_size = (screen.get_width(), screen.get_height())
329 window.set_size_request(*screen_size)
330
331 label_trough = gtk.VBox()
332 label_trough.set_spacing(0)
333
334 rhs_box = gtk.EventBox()
335 rhs_box.modify_bg(gtk.STATE_NORMAL, _LABEL_TROUGH_COLOR)
336 rhs_box.add(label_trough)
337
338 console_box = gtk.EventBox()
339 console_box.set_size_request(-1, 180)
340 console_box.modify_bg(gtk.STATE_NORMAL, _BLACK)
341
342 notest_label = make_notest_label()
343
344 test_widget_box = gtk.Alignment(xalign=0.5, yalign=0.5)
345 test_widget_box.set_size_request(-1, -1)
346 test_widget_box.add(notest_label)
347
348 lhs_box = gtk.VBox()
349 lhs_box.pack_end(console_box, False, False)
350 lhs_box.pack_start(test_widget_box)
351 lhs_box.pack_start(make_hsep(3), False, False)
352
353 base_box = gtk.HBox()
354 base_box.pack_end(rhs_box, False, False)
355 base_box.pack_end(make_vsep(3), False, False)
356 base_box.pack_start(lhs_box)
357
358 window.connect('key-release-event', handle_key_release_event)
359 window.add_events(gtk.gdk.KEY_RELEASE_MASK)
360
361 # On startup, get general configuration data from the autotest
362 # control program, specifically the list of tests to run (in
363 # order) and some filenames.
364 XXX_log('pulling control info')
365 test_list = control_recv()
366 status_file_path = control_recv()
367 log_file_path = control_recv()
368
369 status_map = StatusMap(status_file_path, test_list)
370
371 for test in test_list:
372 tlb = TestLabelBox(test)
373 status_map.set_label(test, tlb)
374 label_trough.pack_start(tlb, False, False)
375 label_trough.pack_start(make_hsep(), False, False)
376 for subtest in test.automated_seq:
377 stlb = SubTestLabelBox(subtest)
378 status_map.set_label(subtest, stlb)
379
380 window.add(base_box)
381 window.show_all()
382
383 test_widget_allocation = test_widget_box.get_allocation()
384 test_widget_size = (test_widget_allocation.width,
385 test_widget_allocation.height)
386 XXX_log('test_widget_size = %s' % repr(test_widget_size))
387 control_send(test_widget_size)
388
389 trigger_dict = dict((test.trigger, test) for test in test_list)
390
391 gobject.io_add_watch(sys.stdin, gobject.IO_IN, stdin_callback)
392
393 console = console_proc(console_box.get_allocation(), log_file_path)
394
395 XXX_log('finished ui setup')
396
397 # Test selection is driven either by triggers or by the
398 # remaining_tests_queue. If a trigger was seen, explicitly run
399 # the corresponding test. Otherwise choose the next test from the
400 # queue. Tests are removed from the queue as they are run,
401 # regarless of the outcome. Tests that are interrupted by trigger
402 # are treated as having failed.
403 #
404 # Iterations in the main loop here are driven by data availability
405 # on stdin, which is used to communicate with the autotest control
406 # program. On each step through the loop, a trigger is received
407 # (possibly None) to indicate how the next test should be selected.
408
409 while True:
410 command, arg = control_recv()
411 XXX_log('ui received command %s(%s)' % (command, arg))
412 if command == 'switch_to':
413 next_test = trigger_dict.get(arg, None)
414 elif command == 'next_test':
415 next_test = status_map.next_untested()
416 else:
417 XXX_log('ui command unknown, exiting...')
418 break
419 control_send_target_test_update(
420 next_test, status_map.lookup_count(next_test) + 1)
421 if next_test.automated_seq:
422 XXX_log('ui starting automated_seq')
423 test_widget_box.remove(notest_label)
424 as_widget = make_automated_seq_widget(next_test, status_map)
425 test_widget_box.add(as_widget)
426 window.show_all()
427 gtk.main()
428 command = control_recv()
429 XXX_log('ui automated_seq cmd (%s)' % command)
430 test_widget_box.remove(as_widget)
431 test_widget_box.add(notest_label)
432 window.show_all()
433 XXX_log('ui exiting automated_seq')
434 else:
435 gtk.main()
436
437 # Tell the control process we are done.
438 control_send((None, 0))
439
440 XXX_log('exiting ui')
441
442 if __name__ == '__main__':
443
444 # In global scope, get the test_data class description from the
445 # control program -- this allows a convenient single point of
446 # definition for this class.
447 test_data_class_def = control_recv()
448 exec(test_data_class_def)
449
450 main()
OLDNEW
« no previous file with comments | « client/deps/factory/startx.sh ('k') | client/site_tests/factory_Display/factory_Display.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698