blob: a587d5cfa31a35c28abbfdade16df02c2fa680c5 [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 logging
14import os
15import pickle
16import pipes
17import re
18import signal
19import subprocess
20import sys
21import tempfile
22import threading
23import time
24import traceback
Hung-Te Linf2f78f72012-02-08 19:27:11 +080025from collections import deque
26from optparse import OptionParser
27from Queue import Queue
28
29import factory_common
30from autotest_lib.client.bin import prespawner
31from autotest_lib.client.cros import factory
32from autotest_lib.client.cros.factory import state
33from autotest_lib.client.cros.factory import TestState
34from autotest_lib.client.cros.factory.event import Event
35from autotest_lib.client.cros.factory.event import EventClient
36from autotest_lib.client.cros.factory.event import EventServer
37
38
39SCRIPT_PATH = os.path.realpath(__file__)
40CROS_FACTORY_LIB_PATH = os.path.dirname(SCRIPT_PATH)
Hung-Te Lin6bb48552012-02-09 14:37:43 +080041FACTORY_UI_PATH = os.path.join(CROS_FACTORY_LIB_PATH, 'ui')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080042CLIENT_PATH = os.path.realpath(os.path.join(CROS_FACTORY_LIB_PATH, '..', '..'))
43DEFAULT_TEST_LIST_PATH = os.path.join(
44 CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
45HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
46
Jon Salz758e6cc2012-04-03 15:47:07 +080047GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
48You are running Goofy inside the chroot. Autotests are not supported.
49
50To use Goofy in the chroot, first install an Xvnc server:
51
52 sudo apt-get install tightvncserver
53
54...and then start a VNC X server outside the chroot:
55
56 vncserver :10 &
57 vncviewer :10
58
59...and run Goofy as follows:
60
61 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
62''' + ('*' * 70)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080063
64def get_hwid_cfg():
65 '''
66 Returns the HWID config tag, or an empty string if none can be found.
67 '''
68 if 'CROS_HWID' in os.environ:
69 return os.environ['CROS_HWID']
70 if os.path.exists(HWID_CFG_PATH):
71 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
72 return hwid_cfg_handle.read().strip()
73 return ''
74
75
76def find_test_list():
77 '''
78 Returns the path to the active test list, based on the HWID config tag.
79 '''
80 hwid_cfg = get_hwid_cfg()
81
82 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
83 if hwid_cfg:
84 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
85 if os.path.exists(test_list):
86 logging.info('Using special test list: %s', test_list)
87 return test_list
88 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
89
90 test_list = DEFAULT_TEST_LIST_PATH
91 if os.path.exists(test_list):
92 return test_list
93
94 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
95 if os.path.exists(test_list):
96 logging.info('Using default test list: ' + test_list)
97 return test_list
98 logging.info('ERROR: Cannot find any test list.')
99
100
101def is_process_alive(pid):
102 '''
103 Returns true if the named process is alive and not a zombie.
104 '''
105 try:
106 with open("/proc/%d/stat" % pid) as f:
107 return f.readline().split()[2] != 'Z'
108 except IOError:
109 return False
110
111
112def kill_process_tree(process, caption):
113 '''
114 Kills a process and all its subprocesses.
115
116 @param process: The process to kill (opened with the subprocess module).
117 @param caption: A caption describing the process.
118 '''
Hung-Te Lin1b094702012-03-15 18:14:15 +0800119 # os.kill does not kill child processes. os.killpg kills all processes
120 # sharing same group (and is usually used for killing process tree). But in
121 # our case, to preserve PGID for autotest and upstart service, we need to
122 # iterate through each level until leaf of the tree.
123
124 def get_all_pids(root):
125 ps_output = subprocess.Popen(['ps','--no-headers','-eo','pid,ppid'],
126 stdout=subprocess.PIPE)
127 children = {}
128 for line in ps_output.stdout:
129 match = re.findall('\d+', line)
130 children.setdefault(int(match[1]), []).append(int(match[0]))
131 pids = []
132 def add_children(pid):
133 pids.append(pid)
134 map(add_children, children.get(pid, []))
135 add_children(root)
Hung-Te Lin02988092012-03-16 12:35:17 +0800136 # Reverse the list to first kill children then parents.
137 # Note reversed(pids) will return an iterator instead of real list, so
138 # we must explicitly call pids.reverse() here.
139 pids.reverse()
140 return pids
Hung-Te Lin1b094702012-03-15 18:14:15 +0800141
Hung-Te Lin02988092012-03-16 12:35:17 +0800142 pids = get_all_pids(process.pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800143 for sig in [signal.SIGTERM, signal.SIGKILL]:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800144 logging.info('Stopping %s (pid=%s)...', caption, sorted(pids))
145
146 for i in range(25): # Try 25 times (200 ms between tries)
147 for pid in pids:
148 try:
Hung-Te Lin02988092012-03-16 12:35:17 +0800149 logging.info("Sending signal %s to %d", sig, pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800150 os.kill(pid, sig)
151 except OSError:
152 pass
Jon Salza751ce52012-03-15 14:59:33 +0800153 pids = filter(is_process_alive, pids)
154 if not pids:
155 return
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800156 time.sleep(0.2) # Sleep 200 ms and try again
157
158 logging.warn('Failed to stop %s process. Ignoring.', caption)
159
160
161class TestInvocation(object):
162 '''
163 State for an active test.
164 '''
165 def __init__(self, goofy, test, on_completion=None):
166 '''Constructor.
167
168 @param goofy: The controlling Goofy object.
169 @param test: The FactoryTest object to test.
170 @param on_completion: Callback to invoke in the goofy event queue
171 on completion.
172 '''
173 self.goofy = goofy
174 self.test = test
175 self.thread = threading.Thread(target=self._run)
176 self.on_completion = on_completion
177
178 self._lock = threading.Lock()
179 # The following properties are guarded by the lock.
180 self._aborted = False
181 self._completed = False
182 self._process = None
183
184 def start(self):
185 '''Starts the test thread.'''
186 self.thread.start()
187
188 def abort_and_join(self):
189 '''
190 Aborts a test (must be called from the event controller thread).
191 '''
192 with self._lock:
193 self._aborted = True
194 if self._process:
195 kill_process_tree(self._process, 'autotest')
196 if self.thread:
197 self.thread.join()
198 with self._lock:
199 # Should be set by the thread itself, but just in case...
200 self._completed = True
201
202 def is_completed(self):
203 '''
204 Returns true if the test has finished.
205 '''
206 with self._lock:
207 return self._completed
208
209 def _invoke_autotest(self, test, dargs):
210 '''
211 Invokes an autotest test.
212
213 This method encapsulates all the magic necessary to run a single
214 autotest test using the 'autotest' command-line tool and get a
215 sane pass/fail status and error message out. It may be better
216 to just write our own command-line wrapper for job.run_test
217 instead.
218
219 @param test: the autotest to run
220 @param dargs: the argument map
221 @return: tuple of status (TestState.PASSED or TestState.FAILED) and
222 error message, if any
223 '''
Jon Salz758e6cc2012-04-03 15:47:07 +0800224 if self.goofy.options.no_autotest:
225 logging.warn('In --noautotest mode; not running %s' %
226 self.test.autotest_name)
227
228 time.sleep(2)
229 return TestState.PASSED, 'Passed'
230
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800231 status = TestState.FAILED
232 error_msg = 'Unknown'
233
234 try:
235 client_dir = CLIENT_PATH
236
237 output_dir = '%s/results/%s' % (client_dir, test.path)
238 if not os.path.exists(output_dir):
239 os.makedirs(output_dir)
240 tmp_dir = tempfile.mkdtemp(prefix='tmp', dir=output_dir)
241
242 control_file = os.path.join(tmp_dir, 'control')
243 result_file = os.path.join(tmp_dir, 'result')
244 args_file = os.path.join(tmp_dir, 'args')
245
246 with open(args_file, 'w') as f:
247 pickle.dump(dargs, f)
248
249 # Create a new control file to use to run the test
250 with open(control_file, 'w') as f:
251 print >> f, 'import common, traceback, utils'
252 print >> f, 'import cPickle as pickle'
253 print >> f, ("success = job.run_test("
254 "'%s', **pickle.load(open('%s')))" % (
255 test.autotest_name, args_file))
256
257 print >> f, (
258 "pickle.dump((success, "
259 "str(job.last_error) if job.last_error else None), "
260 "open('%s', 'w'), protocol=2)"
261 % result_file)
262
263 args = ['%s/bin/autotest' % client_dir,
264 '--output_dir', output_dir,
265 control_file]
266
267 factory.console.info('Running test %s' % test.path)
268 logging.debug('Test command line: %s', ' '.join(
269 [pipes.quote(arg) for arg in args]))
270
271 with self._lock:
272 self._process = prespawner.spawn(
273 args, {'CROS_FACTORY_TEST_PATH': test.path})
274
275 returncode = self._process.wait()
276 with self._lock:
277 if self._aborted:
278 error_msg = 'Aborted by operator'
279 return
280
281 if returncode:
282 # Only happens when there is an autotest-level problem (not when
283 # the test actually failed).
284 error_msg = 'autotest returned with code %d' % returncode
285 return
286
287 with open(result_file) as f:
288 try:
289 success, error_msg = pickle.load(f)
290 except: # pylint: disable=W0702
291 logging.exception('Unable to retrieve autotest results')
292 error_msg = 'Unable to retrieve autotest results'
293 return
294
295 if success:
296 status = TestState.PASSED
297 error_msg = ''
298 except Exception: # pylint: disable=W0703
299 traceback.print_exc(sys.stderr)
300 finally:
301 factory.console.info('Test %s: %s' % (test.path, status))
302 return status, error_msg # pylint: disable=W0150
303
304 def _run(self):
305 with self._lock:
306 if self._aborted:
307 return
308
309 count = self.test.update_state(
310 status=TestState.ACTIVE, increment_count=1, error_msg='').count
311
312 test_tag = '%s_%s' % (self.test.path, count)
313 dargs = dict(self.test.dargs)
314 dargs.update({'tag': test_tag,
315 'test_list_path': self.goofy.test_list_path})
316
317 status, error_msg = self._invoke_autotest(self.test, dargs)
318 self.test.update_state(status=status, error_msg=error_msg,
319 visible=False)
320 with self._lock:
321 self._completed = True
322 self.goofy.run_queue.put(self.goofy.reap_completed_tests)
323 self.goofy.run_queue.put(self.on_completion)
324
325
326class Goofy(object):
327 '''
328 The main factory flow.
329
330 Note that all methods in this class must be invoked from the main
331 (event) thread. Other threads, such as callbacks and TestInvocation
332 methods, should instead post events on the run queue.
333
334 TODO: Unit tests. (chrome-os-partner:7409)
335
336 Properties:
337 state_instance: An instance of FactoryState.
338 state_server: The FactoryState XML/RPC server.
339 state_server_thread: A thread running state_server.
340 event_server: The EventServer socket server.
341 event_server_thread: A thread running event_server.
342 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800343 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800344 run_queue: A queue of callbacks to invoke from the main thread.
345 invocations: A map from FactoryTest objects to the corresponding
346 TestInvocations objects representing active tests.
347 tests_to_run: A deque of tests that should be run when the current
348 test(s) complete.
349 options: Command-line options.
350 args: Command-line args.
351 test_list_path: The path to the test list.
352 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800353 event_handlers: Map of Event.Type to the method used to handle that
354 event. If the method has an 'event' argument, the event is passed
355 to the handler.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800356 '''
357 def __init__(self):
358 self.state_instance = None
359 self.state_server = None
360 self.state_server_thread = None
361 self.event_server = None
362 self.event_server_thread = None
363 self.event_client = None
364 self.ui_process = None
365 self.run_queue = Queue()
366 self.invocations = {}
367 self.tests_to_run = deque()
368 self.visible_test = None
369
370 self.options = None
371 self.args = None
372 self.test_list_path = None
373 self.test_list = None
374
Jon Salz0405ab52012-03-16 15:26:52 +0800375 self.event_handlers = {
376 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800377 Event.Type.SHOW_NEXT_ACTIVE_TEST:
378 lambda event: self.show_next_active_test(),
379 Event.Type.RESTART_TESTS:
380 lambda event: self.restart_tests(),
381 Event.Type.AUTO_RUN:
382 lambda event: self.auto_run(),
383 Event.Type.RE_RUN_FAILED:
384 lambda event: self.re_run_failed(),
385 Event.Type.REVIEW:
386 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800387 }
388
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800389 def __del__(self):
390 if self.ui_process:
391 kill_process_tree(self.ui_process, 'ui')
392 if self.state_server_thread:
393 logging.info('Stopping state server')
394 self.state_server.shutdown()
395 self.state_server_thread.join()
396 if self.event_server_thread:
397 logging.info('Stopping event server')
398 self.event_server.shutdown() # pylint: disable=E1101
399 self.event_server_thread.join()
400 prespawner.stop()
401
402 def start_state_server(self):
403 self.state_instance, self.state_server = state.create_server()
404 logging.info('Starting state server')
405 self.state_server_thread = threading.Thread(
406 target=self.state_server.serve_forever)
407 self.state_server_thread.start()
408
409 def start_event_server(self):
410 self.event_server = EventServer()
411 logging.info('Starting factory event server')
412 self.event_server_thread = threading.Thread(
413 target=self.event_server.serve_forever) # pylint: disable=E1101
414 self.event_server_thread.start()
415
416 self.event_client = EventClient(
417 callback=self.handle_event, event_loop=self.run_queue)
418
419 def start_ui(self):
420 ui_proc_args = [FACTORY_UI_PATH, self.test_list_path]
Jon Salz14bcbb02012-03-17 15:11:50 +0800421 if self.options.verbose:
422 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800423 logging.info('Starting ui %s', ui_proc_args)
424 self.ui_process = subprocess.Popen(ui_proc_args)
425 logging.info('Waiting for UI to come up...')
426 self.event_client.wait(
427 lambda event: event.type == Event.Type.UI_READY)
428 logging.info('UI has started')
429
430 def set_visible_test(self, test):
431 if self.visible_test == test:
432 return
433
434 if test:
435 test.update_state(visible=True)
436 if self.visible_test:
437 self.visible_test.update_state(visible=False)
438 self.visible_test = test
439
Jon Salz74ad3262012-03-16 14:40:55 +0800440 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800441 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800442 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800443
Jon Salz74ad3262012-03-16 14:40:55 +0800444 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800445 @param state: The test state.
446 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800447 state = test.update_state(increment_shutdown_count=1)
448 logging.info('Detected shutdown (%d of %d)',
449 state.shutdown_count, test.iterations)
450 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800451 # Good!
452 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800453 elif state.shutdown_count > test.iterations:
454 # Shutdowned too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800455 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800456 error_msg='Too many shutdowns')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800457 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800458 # Need to shutdown again
Chinyue Chene5c5dbf2012-03-19 15:54:54 +0800459 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800460
461 def init_states(self):
462 '''
463 Initializes all states on startup.
464 '''
465 for test in self.test_list.get_all_tests():
466 # Make sure the state server knows about all the tests,
467 # defaulting to an untested state.
468 test.update_state(update_parent=False, visible=False)
469
470 # Any 'active' tests should be marked as failed now.
471 for test in self.test_list.walk():
472 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800473 if state.status != TestState.ACTIVE:
474 continue
475 if isinstance(test, factory.ShutdownStep):
476 # Shutdown while the test was active - that's good.
477 self.handle_shutdown_complete(test, state)
478 else:
479 test.update_state(status=TestState.FAILED,
480 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800481
482 def show_next_active_test(self):
483 '''
484 Rotates to the next visible active test.
485 '''
486 self.reap_completed_tests()
487 active_tests = [
488 t for t in self.test_list.walk()
489 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
490 if not active_tests:
491 return
492
493 try:
494 next_test = active_tests[
495 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
496 except ValueError: # visible_test not present in active_tests
497 next_test = active_tests[0]
498
499 self.set_visible_test(next_test)
500
501 def handle_event(self, event):
502 '''
503 Handles an event from the event server.
504 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800505 handler = self.event_handlers.get(event.type)
506 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800507 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800508 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800509 # We don't register handlers for all event types - just ignore
510 # this event.
511 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512
513 def run_next_test(self):
514 '''
515 Runs the next eligible test (or tests) in self.tests_to_run.
516 '''
517 self.reap_completed_tests()
518 while self.tests_to_run:
519 logging.debug('Tests to run: %s',
520 [x.path for x in self.tests_to_run])
521
522 test = self.tests_to_run[0]
523
524 if test in self.invocations:
525 logging.info('Next test %s is already running', test.path)
526 self.tests_to_run.popleft()
527 return
528
529 if self.invocations and not (test.backgroundable and all(
530 [x.backgroundable for x in self.invocations])):
531 logging.debug('Waiting for non-backgroundable tests to '
532 'complete before running %s', test.path)
533 return
534
535 self.tests_to_run.popleft()
536
Jon Salz74ad3262012-03-16 14:40:55 +0800537 if isinstance(test, factory.ShutdownStep):
Jon Salz758e6cc2012-04-03 15:47:07 +0800538 if factory.in_chroot():
539 logging.warn('In chroot: not shutting down')
540 test.update_state(status=TestState.PASSED,
541 increment_count=1)
542 continue
543
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800544 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800545 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800546 # Save pending test list in the state server
547 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800548 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800549 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800550
551 self.shutdown(test.operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800552
553 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
554 self.invocations[test] = invoc
555 if self.visible_test is None and test.has_ui:
556 self.set_visible_test(test)
557 invoc.start()
558
Jon Salz0405ab52012-03-16 15:26:52 +0800559 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800560 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800561 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800562
563 The tests are run in order unless one fails (then stops).
564 Backgroundable tests are run simultaneously; when a foreground test is
565 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800566
567 @param subtrees: Node or nodes containing tests to run (may either be
568 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800569 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800570 if type(subtrees) != list:
571 subtrees = [subtrees]
572
573 # Nodes we've seen so far, to avoid duplicates.
574 seen = set()
575
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800576 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800577 for subtree in subtrees:
578 for test in subtree.walk():
579 if test in seen:
580 continue
581 seen.add(test)
582
583 if not test.is_leaf():
584 continue
585 if (untested_only and
586 test.get_state().status != TestState.UNTESTED):
587 continue
588 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800589 self.run_next_test()
590
591 def reap_completed_tests(self):
592 '''
593 Removes completed tests from the set of active tests.
594
595 Also updates the visible test if it was reaped.
596 '''
597 for t, v in dict(self.invocations).iteritems():
598 if v.is_completed():
599 del self.invocations[t]
600
601 if (self.visible_test is None or
602 self.visible_test not in self.invocations):
603 self.set_visible_test(None)
604 # Make the first running test, if any, the visible test
605 for t in self.test_list.walk():
606 if t in self.invocations:
607 self.set_visible_test(t)
608 break
609
Hung-Te Lin96632362012-03-20 21:14:18 +0800610 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800611 '''
612 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800613
614 @param abort: True to change state of killed tests to FAILED, False for
615 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800616 '''
617 self.reap_completed_tests()
618 for test, invoc in self.invocations.items():
619 factory.console.info('Killing active test %s...' % test.path)
620 invoc.abort_and_join()
621 factory.console.info('Killed %s' % test.path)
622 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800623 if not abort:
624 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800625 self.reap_completed_tests()
626
Hung-Te Lin96632362012-03-20 21:14:18 +0800627 def abort_active_tests(self):
628 self.kill_active_tests(True)
629
Jon Salz74ad3262012-03-16 14:40:55 +0800630 def shutdown(self, operation):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800631 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800632 Shuts the machine (from a ShutdownStep).
633
634 Args:
635 operation: 'reboot' or 'halt'.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800636 '''
Jon Salz758e6cc2012-04-03 15:47:07 +0800637 if factory.in_chroot():
638 assert False, 'Shutdown not supported in chroot'
639
Jon Salz74ad3262012-03-16 14:40:55 +0800640 assert operation in ['reboot', 'halt']
641 logging.info('Shutting down: %s', operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800642 subprocess.check_call('sync')
Jon Salz74ad3262012-03-16 14:40:55 +0800643 subprocess.check_call(operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800644 time.sleep(30)
Jon Salz74ad3262012-03-16 14:40:55 +0800645 assert False, 'Never reached (should %s)' % operation
646
647 def reboot(self):
648 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800649
650 def main(self):
651 parser = OptionParser()
652 parser.add_option('-v', '--verbose', dest='verbose',
653 action='store_true',
654 help='Enable debug logging')
655 parser.add_option('--print_test_list', dest='print_test_list',
656 metavar='FILE',
657 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800658 parser.add_option('--restart', dest='restart',
659 action='store_true',
660 help='Clear all test state')
661 parser.add_option('--noautotest', dest='no_autotest',
662 action='store_true',
663 help=("Don't actually run autotests "
664 "(defaults to true in chroot)"),
665 default=factory.in_chroot())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800666 (self.options, self.args) = parser.parse_args()
667
668 factory.init_logging('goofy', verbose=self.options.verbose)
669
Jon Salz758e6cc2012-04-03 15:47:07 +0800670 if factory.in_chroot() and (
671 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
672 # That's not going to work! Tell the user how to run
673 # this way.
674 logging.warn(GOOFY_IN_CHROOT_WARNING)
675 time.sleep(1)
676
677 if self.options.restart:
678 state.clear_state()
679
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800680 if self.options.print_test_list:
681 print (factory.read_test_list(self.options.print_test_list).
682 __repr__(recursive=True))
683 return
684
685 logging.info('Started')
686
687 self.start_state_server()
688 # Update HWID configuration tag.
689 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
690
691 self.test_list_path = find_test_list()
692 self.test_list = factory.read_test_list(self.test_list_path,
693 self.state_instance)
694 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
695
696 self.init_states()
697 self.start_event_server()
698 self.start_ui()
699 prespawner.start()
700
701 def state_change_callback(test, state):
702 self.event_client.post_event(
703 Event(Event.Type.STATE_CHANGE,
704 path=test.path, state=state))
705 self.test_list.state_change_callback = state_change_callback
706
707 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800708 tests_after_shutdown = self.state_instance.get_shared_data(
709 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800710 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800711 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800712
Jon Salz758e6cc2012-04-03 15:47:07 +0800713 if tests_after_shutdown is not None:
714 logging.info('Resuming tests after shutdown: %s',
715 tests_after_shutdown)
716 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800717 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800718 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800719 self.run_next_test()
720 else:
721 self.run_tests(self.test_list, untested_only=True)
722
723 # Process events forever.
724 while True:
725 event = self.run_queue.get()
726 if not event:
727 # Shutdown request (although this currently never happens).
728 self.run_queue.task_done()
729 break
730
731 try:
732 event()
733 except Exception as e: # pylint: disable=W0703
734 logging.error('Error in event loop: %s', e)
735 traceback.print_exc(sys.stderr)
736 # But keep going
737 finally:
738 self.run_queue.task_done()
739
Jon Salz0405ab52012-03-16 15:26:52 +0800740 def run_tests_with_status(self, *statuses_to_run):
741 '''Runs all top-level tests with a particular status.
742
743 All active tests, plus any tests to re-run, are reset.
744 '''
745 tests_to_reset = []
746 tests_to_run = []
747
748 for test in self.test_list.get_top_level_tests():
749 status = test.get_state().status
750 if status == TestState.ACTIVE or status in statuses_to_run:
751 # Reset the test (later; we will need to abort
752 # all active tests first).
753 tests_to_reset.append(test)
754 if status in statuses_to_run:
755 tests_to_run.append(test)
756
757 self.abort_active_tests()
758
759 # Reset all statuses of the tests to run (in case any tests were active;
760 # we want them to be run again).
761 for test_to_reset in tests_to_reset:
762 for test in test_to_reset.walk():
763 test.update_state(status=TestState.UNTESTED)
764
765 self.run_tests(tests_to_run, untested_only=True)
766
767 def restart_tests(self):
768 '''Restarts all tests.'''
769 self.abort_active_tests()
770 for test in self.test_list.walk():
771 test.update_state(status=TestState.UNTESTED)
772 self.run_tests(self.test_list)
773
774 def auto_run(self):
775 '''"Auto-runs" tests that have not been run yet.'''
776 self.run_tests_with_status(TestState.UNTESTED, TestState.ACTIVE)
777
778 def re_run_failed(self):
779 '''Re-runs failed tests.'''
780 self.run_tests_with_status(TestState.FAILED)
781
Jon Salz968e90b2012-03-18 16:12:43 +0800782 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800783 '''Event handler for showing review information screen.
784
785 The information screene is rendered by main UI program (ui.py), so in
786 goofy we only need to kill all active tests, set them as untested, and
787 clear remaining tests.
788 '''
789 self.kill_active_tests(False)
790 self.run_tests([])
791
Jon Salz0405ab52012-03-16 15:26:52 +0800792 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800793 '''Switches to a particular test.
794
795 @param event: The SWITCH_TEST event.
796 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800797 test = self.test_list.lookup_path(event.path)
798 if test:
799 invoc = self.invocations.get(test)
800 if invoc and test.backgroundable:
801 # Already running: just bring to the front if it
802 # has a UI.
803 logging.info('Setting visible test to %s', test.path)
804 self.event_client.post_event(
805 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
Jon Salz968e90b2012-03-18 16:12:43 +0800806 return
Jon Salz0405ab52012-03-16 15:26:52 +0800807 self.abort_active_tests()
808 for t in test.walk():
809 t.update_state(status=TestState.UNTESTED)
810 self.run_tests(test)
811 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800812 logging.error('Unknown test %r', event.key)
Jon Salz0405ab52012-03-16 15:26:52 +0800813
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800814
815if __name__ == '__main__':
816 Goofy().main()