blob: 6b89b11e371fe1057dad03b369afd4bc292accac [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
6# Use of this source code is governed by a BSD-style license that can be
7# found in the LICENSE file.
8
9'''
10The main factory flow that runs the factory test and finalizes a device.
11'''
12
Jon Salz0405ab52012-03-16 15:26:52 +080013import inspect
14import logging
15import os
16import pickle
17import pipes
18import re
19import signal
20import subprocess
21import sys
22import tempfile
23import threading
24import time
25import traceback
Hung-Te Linf2f78f72012-02-08 19:27:11 +080026from collections import deque
27from optparse import OptionParser
28from Queue import Queue
29
30import factory_common
31from autotest_lib.client.bin import prespawner
32from autotest_lib.client.cros import factory
33from autotest_lib.client.cros.factory import state
34from autotest_lib.client.cros.factory import TestState
35from autotest_lib.client.cros.factory.event import Event
36from autotest_lib.client.cros.factory.event import EventClient
37from autotest_lib.client.cros.factory.event import EventServer
38
39
40SCRIPT_PATH = os.path.realpath(__file__)
41CROS_FACTORY_LIB_PATH = os.path.dirname(SCRIPT_PATH)
Hung-Te Lin6bb48552012-02-09 14:37:43 +080042FACTORY_UI_PATH = os.path.join(CROS_FACTORY_LIB_PATH, 'ui')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080043CLIENT_PATH = os.path.realpath(os.path.join(CROS_FACTORY_LIB_PATH, '..', '..'))
44DEFAULT_TEST_LIST_PATH = os.path.join(
45 CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
46HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
47
48
49def get_hwid_cfg():
50 '''
51 Returns the HWID config tag, or an empty string if none can be found.
52 '''
53 if 'CROS_HWID' in os.environ:
54 return os.environ['CROS_HWID']
55 if os.path.exists(HWID_CFG_PATH):
56 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
57 return hwid_cfg_handle.read().strip()
58 return ''
59
60
61def find_test_list():
62 '''
63 Returns the path to the active test list, based on the HWID config tag.
64 '''
65 hwid_cfg = get_hwid_cfg()
66
67 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
68 if hwid_cfg:
69 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
70 if os.path.exists(test_list):
71 logging.info('Using special test list: %s', test_list)
72 return test_list
73 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
74
75 test_list = DEFAULT_TEST_LIST_PATH
76 if os.path.exists(test_list):
77 return test_list
78
79 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
80 if os.path.exists(test_list):
81 logging.info('Using default test list: ' + test_list)
82 return test_list
83 logging.info('ERROR: Cannot find any test list.')
84
85
86def is_process_alive(pid):
87 '''
88 Returns true if the named process is alive and not a zombie.
89 '''
90 try:
91 with open("/proc/%d/stat" % pid) as f:
92 return f.readline().split()[2] != 'Z'
93 except IOError:
94 return False
95
96
97def kill_process_tree(process, caption):
98 '''
99 Kills a process and all its subprocesses.
100
101 @param process: The process to kill (opened with the subprocess module).
102 @param caption: A caption describing the process.
103 '''
Hung-Te Lin1b094702012-03-15 18:14:15 +0800104 # os.kill does not kill child processes. os.killpg kills all processes
105 # sharing same group (and is usually used for killing process tree). But in
106 # our case, to preserve PGID for autotest and upstart service, we need to
107 # iterate through each level until leaf of the tree.
108
109 def get_all_pids(root):
110 ps_output = subprocess.Popen(['ps','--no-headers','-eo','pid,ppid'],
111 stdout=subprocess.PIPE)
112 children = {}
113 for line in ps_output.stdout:
114 match = re.findall('\d+', line)
115 children.setdefault(int(match[1]), []).append(int(match[0]))
116 pids = []
117 def add_children(pid):
118 pids.append(pid)
119 map(add_children, children.get(pid, []))
120 add_children(root)
Hung-Te Lin02988092012-03-16 12:35:17 +0800121 # Reverse the list to first kill children then parents.
122 # Note reversed(pids) will return an iterator instead of real list, so
123 # we must explicitly call pids.reverse() here.
124 pids.reverse()
125 return pids
Hung-Te Lin1b094702012-03-15 18:14:15 +0800126
Hung-Te Lin02988092012-03-16 12:35:17 +0800127 pids = get_all_pids(process.pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800128 for sig in [signal.SIGTERM, signal.SIGKILL]:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800129 logging.info('Stopping %s (pid=%s)...', caption, sorted(pids))
130
131 for i in range(25): # Try 25 times (200 ms between tries)
132 for pid in pids:
133 try:
Hung-Te Lin02988092012-03-16 12:35:17 +0800134 logging.info("Sending signal %s to %d", sig, pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800135 os.kill(pid, sig)
136 except OSError:
137 pass
Jon Salza751ce52012-03-15 14:59:33 +0800138 pids = filter(is_process_alive, pids)
139 if not pids:
140 return
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800141 time.sleep(0.2) # Sleep 200 ms and try again
142
143 logging.warn('Failed to stop %s process. Ignoring.', caption)
144
145
146class TestInvocation(object):
147 '''
148 State for an active test.
149 '''
150 def __init__(self, goofy, test, on_completion=None):
151 '''Constructor.
152
153 @param goofy: The controlling Goofy object.
154 @param test: The FactoryTest object to test.
155 @param on_completion: Callback to invoke in the goofy event queue
156 on completion.
157 '''
158 self.goofy = goofy
159 self.test = test
160 self.thread = threading.Thread(target=self._run)
161 self.on_completion = on_completion
162
163 self._lock = threading.Lock()
164 # The following properties are guarded by the lock.
165 self._aborted = False
166 self._completed = False
167 self._process = None
168
169 def start(self):
170 '''Starts the test thread.'''
171 self.thread.start()
172
173 def abort_and_join(self):
174 '''
175 Aborts a test (must be called from the event controller thread).
176 '''
177 with self._lock:
178 self._aborted = True
179 if self._process:
180 kill_process_tree(self._process, 'autotest')
181 if self.thread:
182 self.thread.join()
183 with self._lock:
184 # Should be set by the thread itself, but just in case...
185 self._completed = True
186
187 def is_completed(self):
188 '''
189 Returns true if the test has finished.
190 '''
191 with self._lock:
192 return self._completed
193
194 def _invoke_autotest(self, test, dargs):
195 '''
196 Invokes an autotest test.
197
198 This method encapsulates all the magic necessary to run a single
199 autotest test using the 'autotest' command-line tool and get a
200 sane pass/fail status and error message out. It may be better
201 to just write our own command-line wrapper for job.run_test
202 instead.
203
204 @param test: the autotest to run
205 @param dargs: the argument map
206 @return: tuple of status (TestState.PASSED or TestState.FAILED) and
207 error message, if any
208 '''
209 status = TestState.FAILED
210 error_msg = 'Unknown'
211
212 try:
213 client_dir = CLIENT_PATH
214
215 output_dir = '%s/results/%s' % (client_dir, test.path)
216 if not os.path.exists(output_dir):
217 os.makedirs(output_dir)
218 tmp_dir = tempfile.mkdtemp(prefix='tmp', dir=output_dir)
219
220 control_file = os.path.join(tmp_dir, 'control')
221 result_file = os.path.join(tmp_dir, 'result')
222 args_file = os.path.join(tmp_dir, 'args')
223
224 with open(args_file, 'w') as f:
225 pickle.dump(dargs, f)
226
227 # Create a new control file to use to run the test
228 with open(control_file, 'w') as f:
229 print >> f, 'import common, traceback, utils'
230 print >> f, 'import cPickle as pickle'
231 print >> f, ("success = job.run_test("
232 "'%s', **pickle.load(open('%s')))" % (
233 test.autotest_name, args_file))
234
235 print >> f, (
236 "pickle.dump((success, "
237 "str(job.last_error) if job.last_error else None), "
238 "open('%s', 'w'), protocol=2)"
239 % result_file)
240
241 args = ['%s/bin/autotest' % client_dir,
242 '--output_dir', output_dir,
243 control_file]
244
245 factory.console.info('Running test %s' % test.path)
246 logging.debug('Test command line: %s', ' '.join(
247 [pipes.quote(arg) for arg in args]))
248
249 with self._lock:
250 self._process = prespawner.spawn(
251 args, {'CROS_FACTORY_TEST_PATH': test.path})
252
253 returncode = self._process.wait()
254 with self._lock:
255 if self._aborted:
256 error_msg = 'Aborted by operator'
257 return
258
259 if returncode:
260 # Only happens when there is an autotest-level problem (not when
261 # the test actually failed).
262 error_msg = 'autotest returned with code %d' % returncode
263 return
264
265 with open(result_file) as f:
266 try:
267 success, error_msg = pickle.load(f)
268 except: # pylint: disable=W0702
269 logging.exception('Unable to retrieve autotest results')
270 error_msg = 'Unable to retrieve autotest results'
271 return
272
273 if success:
274 status = TestState.PASSED
275 error_msg = ''
276 except Exception: # pylint: disable=W0703
277 traceback.print_exc(sys.stderr)
278 finally:
279 factory.console.info('Test %s: %s' % (test.path, status))
280 return status, error_msg # pylint: disable=W0150
281
282 def _run(self):
283 with self._lock:
284 if self._aborted:
285 return
286
287 count = self.test.update_state(
288 status=TestState.ACTIVE, increment_count=1, error_msg='').count
289
290 test_tag = '%s_%s' % (self.test.path, count)
291 dargs = dict(self.test.dargs)
292 dargs.update({'tag': test_tag,
293 'test_list_path': self.goofy.test_list_path})
294
295 status, error_msg = self._invoke_autotest(self.test, dargs)
296 self.test.update_state(status=status, error_msg=error_msg,
297 visible=False)
298 with self._lock:
299 self._completed = True
300 self.goofy.run_queue.put(self.goofy.reap_completed_tests)
301 self.goofy.run_queue.put(self.on_completion)
302
303
304class Goofy(object):
305 '''
306 The main factory flow.
307
308 Note that all methods in this class must be invoked from the main
309 (event) thread. Other threads, such as callbacks and TestInvocation
310 methods, should instead post events on the run queue.
311
312 TODO: Unit tests. (chrome-os-partner:7409)
313
314 Properties:
315 state_instance: An instance of FactoryState.
316 state_server: The FactoryState XML/RPC server.
317 state_server_thread: A thread running state_server.
318 event_server: The EventServer socket server.
319 event_server_thread: A thread running event_server.
320 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800321 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800322 run_queue: A queue of callbacks to invoke from the main thread.
323 invocations: A map from FactoryTest objects to the corresponding
324 TestInvocations objects representing active tests.
325 tests_to_run: A deque of tests that should be run when the current
326 test(s) complete.
327 options: Command-line options.
328 args: Command-line args.
329 test_list_path: The path to the test list.
330 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800331 event_handlers: Map of Event.Type to the method used to handle that
332 event. If the method has an 'event' argument, the event is passed
333 to the handler.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800334 '''
335 def __init__(self):
336 self.state_instance = None
337 self.state_server = None
338 self.state_server_thread = None
339 self.event_server = None
340 self.event_server_thread = None
341 self.event_client = None
342 self.ui_process = None
343 self.run_queue = Queue()
344 self.invocations = {}
345 self.tests_to_run = deque()
346 self.visible_test = None
347
348 self.options = None
349 self.args = None
350 self.test_list_path = None
351 self.test_list = None
352
Jon Salz0405ab52012-03-16 15:26:52 +0800353 self.event_handlers = {
354 Event.Type.SWITCH_TEST: self.handle_switch_test,
355 Event.Type.SHOW_NEXT_ACTIVE_TEST: self.show_next_active_test,
356 Event.Type.RESTART_TESTS: self.restart_tests,
357 Event.Type.AUTO_RUN: self.auto_run,
358 Event.Type.RE_RUN_FAILED: self.re_run_failed,
359 }
360
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800361 def __del__(self):
362 if self.ui_process:
363 kill_process_tree(self.ui_process, 'ui')
364 if self.state_server_thread:
365 logging.info('Stopping state server')
366 self.state_server.shutdown()
367 self.state_server_thread.join()
368 if self.event_server_thread:
369 logging.info('Stopping event server')
370 self.event_server.shutdown() # pylint: disable=E1101
371 self.event_server_thread.join()
372 prespawner.stop()
373
374 def start_state_server(self):
375 self.state_instance, self.state_server = state.create_server()
376 logging.info('Starting state server')
377 self.state_server_thread = threading.Thread(
378 target=self.state_server.serve_forever)
379 self.state_server_thread.start()
380
381 def start_event_server(self):
382 self.event_server = EventServer()
383 logging.info('Starting factory event server')
384 self.event_server_thread = threading.Thread(
385 target=self.event_server.serve_forever) # pylint: disable=E1101
386 self.event_server_thread.start()
387
388 self.event_client = EventClient(
389 callback=self.handle_event, event_loop=self.run_queue)
390
391 def start_ui(self):
392 ui_proc_args = [FACTORY_UI_PATH, self.test_list_path]
Jon Salz14bcbb02012-03-17 15:11:50 +0800393 if self.options.verbose:
394 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800395 logging.info('Starting ui %s', ui_proc_args)
396 self.ui_process = subprocess.Popen(ui_proc_args)
397 logging.info('Waiting for UI to come up...')
398 self.event_client.wait(
399 lambda event: event.type == Event.Type.UI_READY)
400 logging.info('UI has started')
401
402 def set_visible_test(self, test):
403 if self.visible_test == test:
404 return
405
406 if test:
407 test.update_state(visible=True)
408 if self.visible_test:
409 self.visible_test.update_state(visible=False)
410 self.visible_test = test
411
Jon Salz74ad3262012-03-16 14:40:55 +0800412 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800413 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800414 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415
Jon Salz74ad3262012-03-16 14:40:55 +0800416 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800417 @param state: The test state.
418 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800419 state = test.update_state(increment_shutdown_count=1)
420 logging.info('Detected shutdown (%d of %d)',
421 state.shutdown_count, test.iterations)
422 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800423 # Good!
424 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800425 elif state.shutdown_count > test.iterations:
426 # Shutdowned too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800427 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800428 error_msg='Too many shutdowns')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800429 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800430 # Need to shutdown again
431 self.shutdown()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800432
433 def init_states(self):
434 '''
435 Initializes all states on startup.
436 '''
437 for test in self.test_list.get_all_tests():
438 # Make sure the state server knows about all the tests,
439 # defaulting to an untested state.
440 test.update_state(update_parent=False, visible=False)
441
442 # Any 'active' tests should be marked as failed now.
443 for test in self.test_list.walk():
444 state = test.get_state()
445 if isinstance(test, factory.InformationScreen):
446 test.update_state(status=TestState.UNTESTED, error_msg='')
447 elif state.status == TestState.ACTIVE:
Jon Salz74ad3262012-03-16 14:40:55 +0800448 if isinstance(test, factory.ShutdownStep):
449 # Shutdown while the test was active - that's good.
450 self.handle_shutdown_complete(test, state)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800451 else:
452 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800453 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800454
455 def show_next_active_test(self):
456 '''
457 Rotates to the next visible active test.
458 '''
459 self.reap_completed_tests()
460 active_tests = [
461 t for t in self.test_list.walk()
462 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
463 if not active_tests:
464 return
465
466 try:
467 next_test = active_tests[
468 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
469 except ValueError: # visible_test not present in active_tests
470 next_test = active_tests[0]
471
472 self.set_visible_test(next_test)
473
474 def handle_event(self, event):
475 '''
476 Handles an event from the event server.
477 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800478 handler = self.event_handlers.get(event.type)
479 if handler:
480 if 'event' in inspect.getargspec(handler).args:
481 handler(event=event)
Tammo Spalinkdb8d7112012-03-13 18:54:37 +0800482 else:
Jon Salz0405ab52012-03-16 15:26:52 +0800483 handler()
484 else:
485 logging.debug('Unbound event type %s' % event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800486
487 def run_next_test(self):
488 '''
489 Runs the next eligible test (or tests) in self.tests_to_run.
490 '''
491 self.reap_completed_tests()
492 while self.tests_to_run:
493 logging.debug('Tests to run: %s',
494 [x.path for x in self.tests_to_run])
495
496 test = self.tests_to_run[0]
497
498 if test in self.invocations:
499 logging.info('Next test %s is already running', test.path)
500 self.tests_to_run.popleft()
501 return
502
503 if self.invocations and not (test.backgroundable and all(
504 [x.backgroundable for x in self.invocations])):
505 logging.debug('Waiting for non-backgroundable tests to '
506 'complete before running %s', test.path)
507 return
508
509 self.tests_to_run.popleft()
510
Jon Salz74ad3262012-03-16 14:40:55 +0800511 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800513 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800514 # Save pending test list in the state server
515 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800516 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800517 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800518
519 self.shutdown(test.operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800520
521 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
522 self.invocations[test] = invoc
523 if self.visible_test is None and test.has_ui:
524 self.set_visible_test(test)
525 invoc.start()
526
Jon Salz0405ab52012-03-16 15:26:52 +0800527 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800528 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800529 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800530
531 The tests are run in order unless one fails (then stops).
532 Backgroundable tests are run simultaneously; when a foreground test is
533 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800534
535 @param subtrees: Node or nodes containing tests to run (may either be
536 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800537 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800538 if type(subtrees) != list:
539 subtrees = [subtrees]
540
541 # Nodes we've seen so far, to avoid duplicates.
542 seen = set()
543
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800544 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800545 for subtree in subtrees:
546 for test in subtree.walk():
547 if test in seen:
548 continue
549 seen.add(test)
550
551 if not test.is_leaf():
552 continue
553 if (untested_only and
554 test.get_state().status != TestState.UNTESTED):
555 continue
556 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800557 self.run_next_test()
558
559 def reap_completed_tests(self):
560 '''
561 Removes completed tests from the set of active tests.
562
563 Also updates the visible test if it was reaped.
564 '''
565 for t, v in dict(self.invocations).iteritems():
566 if v.is_completed():
567 del self.invocations[t]
568
569 if (self.visible_test is None or
570 self.visible_test not in self.invocations):
571 self.set_visible_test(None)
572 # Make the first running test, if any, the visible test
573 for t in self.test_list.walk():
574 if t in self.invocations:
575 self.set_visible_test(t)
576 break
577
578 def abort_active_tests(self):
579 '''
580 Kills and waits for all active tests.
581 '''
582 self.reap_completed_tests()
583 for test, invoc in self.invocations.items():
584 factory.console.info('Killing active test %s...' % test.path)
585 invoc.abort_and_join()
586 factory.console.info('Killed %s' % test.path)
587 del self.invocations[test]
588 self.reap_completed_tests()
589
Jon Salz74ad3262012-03-16 14:40:55 +0800590 def shutdown(self, operation):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800591 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800592 Shuts the machine (from a ShutdownStep).
593
594 Args:
595 operation: 'reboot' or 'halt'.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800596 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800597 assert operation in ['reboot', 'halt']
598 logging.info('Shutting down: %s', operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800599 subprocess.check_call('sync')
Jon Salz74ad3262012-03-16 14:40:55 +0800600 subprocess.check_call(operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800601 time.sleep(30)
Jon Salz74ad3262012-03-16 14:40:55 +0800602 assert False, 'Never reached (should %s)' % operation
603
604 def reboot(self):
605 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800606
607 def main(self):
608 parser = OptionParser()
609 parser.add_option('-v', '--verbose', dest='verbose',
610 action='store_true',
611 help='Enable debug logging')
612 parser.add_option('--print_test_list', dest='print_test_list',
613 metavar='FILE',
614 help='Read and print test list FILE, and exit')
615 (self.options, self.args) = parser.parse_args()
616
617 factory.init_logging('goofy', verbose=self.options.verbose)
618
619 if self.options.print_test_list:
620 print (factory.read_test_list(self.options.print_test_list).
621 __repr__(recursive=True))
622 return
623
624 logging.info('Started')
625
626 self.start_state_server()
627 # Update HWID configuration tag.
628 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
629
630 self.test_list_path = find_test_list()
631 self.test_list = factory.read_test_list(self.test_list_path,
632 self.state_instance)
633 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
634
635 self.init_states()
636 self.start_event_server()
637 self.start_ui()
638 prespawner.start()
639
640 def state_change_callback(test, state):
641 self.event_client.post_event(
642 Event(Event.Type.STATE_CHANGE,
643 path=test.path, state=state))
644 self.test_list.state_change_callback = state_change_callback
645
646 try:
647 tests_after_reboot = self.state_instance.get_shared_data(
648 'tests_after_reboot')
649 except KeyError:
650 tests_after_reboot = None
651
652 if tests_after_reboot is not None:
653 logging.info('Resuming tests after reboot: %s', tests_after_reboot)
654 self.state_instance.set_shared_data('tests_after_reboot', None)
655 self.tests_to_run.extend(
656 self.test_list.lookup_path(t) for t in tests_after_reboot)
657 self.run_next_test()
658 else:
659 self.run_tests(self.test_list, untested_only=True)
660
661 # Process events forever.
662 while True:
663 event = self.run_queue.get()
664 if not event:
665 # Shutdown request (although this currently never happens).
666 self.run_queue.task_done()
667 break
668
669 try:
670 event()
671 except Exception as e: # pylint: disable=W0703
672 logging.error('Error in event loop: %s', e)
673 traceback.print_exc(sys.stderr)
674 # But keep going
675 finally:
676 self.run_queue.task_done()
677
Jon Salz0405ab52012-03-16 15:26:52 +0800678 def run_tests_with_status(self, *statuses_to_run):
679 '''Runs all top-level tests with a particular status.
680
681 All active tests, plus any tests to re-run, are reset.
682 '''
683 tests_to_reset = []
684 tests_to_run = []
685
686 for test in self.test_list.get_top_level_tests():
687 status = test.get_state().status
688 if status == TestState.ACTIVE or status in statuses_to_run:
689 # Reset the test (later; we will need to abort
690 # all active tests first).
691 tests_to_reset.append(test)
692 if status in statuses_to_run:
693 tests_to_run.append(test)
694
695 self.abort_active_tests()
696
697 # Reset all statuses of the tests to run (in case any tests were active;
698 # we want them to be run again).
699 for test_to_reset in tests_to_reset:
700 for test in test_to_reset.walk():
701 test.update_state(status=TestState.UNTESTED)
702
703 self.run_tests(tests_to_run, untested_only=True)
704
705 def restart_tests(self):
706 '''Restarts all tests.'''
707 self.abort_active_tests()
708 for test in self.test_list.walk():
709 test.update_state(status=TestState.UNTESTED)
710 self.run_tests(self.test_list)
711
712 def auto_run(self):
713 '''"Auto-runs" tests that have not been run yet.'''
714 self.run_tests_with_status(TestState.UNTESTED, TestState.ACTIVE)
715
716 def re_run_failed(self):
717 '''Re-runs failed tests.'''
718 self.run_tests_with_status(TestState.FAILED)
719
720 def handle_switch_test(self, event):
721 test = self.test_list.lookup_path(event.path)
722 if test:
723 invoc = self.invocations.get(test)
724 if invoc and test.backgroundable:
725 # Already running: just bring to the front if it
726 # has a UI.
727 logging.info('Setting visible test to %s', test.path)
728 self.event_client.post_event(
729 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
730 self.abort_active_tests()
731 for t in test.walk():
732 t.update_state(status=TestState.UNTESTED)
733 self.run_tests(test)
734 else:
735 logging.error('unknown test %r' % event.key)
736
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800737
738if __name__ == '__main__':
739 Goofy().main()