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