blob: 222679f810f7e67ed7e8fa34a1ca9f60cb0b0803 [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,
Hung-Te Lin96632362012-03-20 21:14:18 +0800359 Event.Type.REVIEW: self.show_review_information,
Jon Salz0405ab52012-03-16 15:26:52 +0800360 }
361
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800362 def __del__(self):
363 if self.ui_process:
364 kill_process_tree(self.ui_process, 'ui')
365 if self.state_server_thread:
366 logging.info('Stopping state server')
367 self.state_server.shutdown()
368 self.state_server_thread.join()
369 if self.event_server_thread:
370 logging.info('Stopping event server')
371 self.event_server.shutdown() # pylint: disable=E1101
372 self.event_server_thread.join()
373 prespawner.stop()
374
375 def start_state_server(self):
376 self.state_instance, self.state_server = state.create_server()
377 logging.info('Starting state server')
378 self.state_server_thread = threading.Thread(
379 target=self.state_server.serve_forever)
380 self.state_server_thread.start()
381
382 def start_event_server(self):
383 self.event_server = EventServer()
384 logging.info('Starting factory event server')
385 self.event_server_thread = threading.Thread(
386 target=self.event_server.serve_forever) # pylint: disable=E1101
387 self.event_server_thread.start()
388
389 self.event_client = EventClient(
390 callback=self.handle_event, event_loop=self.run_queue)
391
392 def start_ui(self):
393 ui_proc_args = [FACTORY_UI_PATH, self.test_list_path]
Jon Salz14bcbb02012-03-17 15:11:50 +0800394 if self.options.verbose:
395 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396 logging.info('Starting ui %s', ui_proc_args)
397 self.ui_process = subprocess.Popen(ui_proc_args)
398 logging.info('Waiting for UI to come up...')
399 self.event_client.wait(
400 lambda event: event.type == Event.Type.UI_READY)
401 logging.info('UI has started')
402
403 def set_visible_test(self, test):
404 if self.visible_test == test:
405 return
406
407 if test:
408 test.update_state(visible=True)
409 if self.visible_test:
410 self.visible_test.update_state(visible=False)
411 self.visible_test = test
412
Jon Salz74ad3262012-03-16 14:40:55 +0800413 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800414 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800415 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800416
Jon Salz74ad3262012-03-16 14:40:55 +0800417 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800418 @param state: The test state.
419 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800420 state = test.update_state(increment_shutdown_count=1)
421 logging.info('Detected shutdown (%d of %d)',
422 state.shutdown_count, test.iterations)
423 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800424 # Good!
425 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800426 elif state.shutdown_count > test.iterations:
427 # Shutdowned too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800428 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800429 error_msg='Too many shutdowns')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800430 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800431 # Need to shutdown again
Chinyue Chene5c5dbf2012-03-19 15:54:54 +0800432 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800433
434 def init_states(self):
435 '''
436 Initializes all states on startup.
437 '''
438 for test in self.test_list.get_all_tests():
439 # Make sure the state server knows about all the tests,
440 # defaulting to an untested state.
441 test.update_state(update_parent=False, visible=False)
442
443 # Any 'active' tests should be marked as failed now.
444 for test in self.test_list.walk():
445 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800446 if state.status != TestState.ACTIVE:
447 continue
448 if isinstance(test, factory.ShutdownStep):
449 # Shutdown while the test was active - that's good.
450 self.handle_shutdown_complete(test, state)
451 else:
452 test.update_state(status=TestState.FAILED,
453 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
Hung-Te Lin96632362012-03-20 21:14:18 +0800578 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579 '''
580 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800581
582 @param abort: True to change state of killed tests to FAILED, False for
583 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800584 '''
585 self.reap_completed_tests()
586 for test, invoc in self.invocations.items():
587 factory.console.info('Killing active test %s...' % test.path)
588 invoc.abort_and_join()
589 factory.console.info('Killed %s' % test.path)
590 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800591 if not abort:
592 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800593 self.reap_completed_tests()
594
Hung-Te Lin96632362012-03-20 21:14:18 +0800595 def abort_active_tests(self):
596 self.kill_active_tests(True)
597
Jon Salz74ad3262012-03-16 14:40:55 +0800598 def shutdown(self, operation):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800599 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800600 Shuts the machine (from a ShutdownStep).
601
602 Args:
603 operation: 'reboot' or 'halt'.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800604 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800605 assert operation in ['reboot', 'halt']
606 logging.info('Shutting down: %s', operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800607 subprocess.check_call('sync')
Jon Salz74ad3262012-03-16 14:40:55 +0800608 subprocess.check_call(operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800609 time.sleep(30)
Jon Salz74ad3262012-03-16 14:40:55 +0800610 assert False, 'Never reached (should %s)' % operation
611
612 def reboot(self):
613 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800614
615 def main(self):
616 parser = OptionParser()
617 parser.add_option('-v', '--verbose', dest='verbose',
618 action='store_true',
619 help='Enable debug logging')
620 parser.add_option('--print_test_list', dest='print_test_list',
621 metavar='FILE',
622 help='Read and print test list FILE, and exit')
623 (self.options, self.args) = parser.parse_args()
624
625 factory.init_logging('goofy', verbose=self.options.verbose)
626
627 if self.options.print_test_list:
628 print (factory.read_test_list(self.options.print_test_list).
629 __repr__(recursive=True))
630 return
631
632 logging.info('Started')
633
634 self.start_state_server()
635 # Update HWID configuration tag.
636 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
637
638 self.test_list_path = find_test_list()
639 self.test_list = factory.read_test_list(self.test_list_path,
640 self.state_instance)
641 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
642
643 self.init_states()
644 self.start_event_server()
645 self.start_ui()
646 prespawner.start()
647
648 def state_change_callback(test, state):
649 self.event_client.post_event(
650 Event(Event.Type.STATE_CHANGE,
651 path=test.path, state=state))
652 self.test_list.state_change_callback = state_change_callback
653
654 try:
655 tests_after_reboot = self.state_instance.get_shared_data(
656 'tests_after_reboot')
657 except KeyError:
658 tests_after_reboot = None
659
660 if tests_after_reboot is not None:
661 logging.info('Resuming tests after reboot: %s', tests_after_reboot)
662 self.state_instance.set_shared_data('tests_after_reboot', None)
663 self.tests_to_run.extend(
664 self.test_list.lookup_path(t) for t in tests_after_reboot)
665 self.run_next_test()
666 else:
667 self.run_tests(self.test_list, untested_only=True)
668
669 # Process events forever.
670 while True:
671 event = self.run_queue.get()
672 if not event:
673 # Shutdown request (although this currently never happens).
674 self.run_queue.task_done()
675 break
676
677 try:
678 event()
679 except Exception as e: # pylint: disable=W0703
680 logging.error('Error in event loop: %s', e)
681 traceback.print_exc(sys.stderr)
682 # But keep going
683 finally:
684 self.run_queue.task_done()
685
Jon Salz0405ab52012-03-16 15:26:52 +0800686 def run_tests_with_status(self, *statuses_to_run):
687 '''Runs all top-level tests with a particular status.
688
689 All active tests, plus any tests to re-run, are reset.
690 '''
691 tests_to_reset = []
692 tests_to_run = []
693
694 for test in self.test_list.get_top_level_tests():
695 status = test.get_state().status
696 if status == TestState.ACTIVE or status in statuses_to_run:
697 # Reset the test (later; we will need to abort
698 # all active tests first).
699 tests_to_reset.append(test)
700 if status in statuses_to_run:
701 tests_to_run.append(test)
702
703 self.abort_active_tests()
704
705 # Reset all statuses of the tests to run (in case any tests were active;
706 # we want them to be run again).
707 for test_to_reset in tests_to_reset:
708 for test in test_to_reset.walk():
709 test.update_state(status=TestState.UNTESTED)
710
711 self.run_tests(tests_to_run, untested_only=True)
712
713 def restart_tests(self):
714 '''Restarts all tests.'''
715 self.abort_active_tests()
716 for test in self.test_list.walk():
717 test.update_state(status=TestState.UNTESTED)
718 self.run_tests(self.test_list)
719
720 def auto_run(self):
721 '''"Auto-runs" tests that have not been run yet.'''
722 self.run_tests_with_status(TestState.UNTESTED, TestState.ACTIVE)
723
724 def re_run_failed(self):
725 '''Re-runs failed tests.'''
726 self.run_tests_with_status(TestState.FAILED)
727
Hung-Te Lin96632362012-03-20 21:14:18 +0800728 def show_review_information(self, event):
729 '''Event handler for showing review information screen.
730
731 The information screene is rendered by main UI program (ui.py), so in
732 goofy we only need to kill all active tests, set them as untested, and
733 clear remaining tests.
734 '''
735 self.kill_active_tests(False)
736 self.run_tests([])
737
Jon Salz0405ab52012-03-16 15:26:52 +0800738 def handle_switch_test(self, event):
739 test = self.test_list.lookup_path(event.path)
740 if test:
741 invoc = self.invocations.get(test)
742 if invoc and test.backgroundable:
743 # Already running: just bring to the front if it
744 # has a UI.
745 logging.info('Setting visible test to %s', test.path)
746 self.event_client.post_event(
747 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
748 self.abort_active_tests()
749 for t in test.walk():
750 t.update_state(status=TestState.UNTESTED)
751 self.run_tests(test)
752 else:
753 logging.error('unknown test %r' % event.key)
754
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800755
756if __name__ == '__main__':
757 Goofy().main()