blob: 3991d81b696c6a470a0028ae3df3840dbf066f75 [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 Salz73e0fd02012-04-04 11:46:38 +080013import array
14import fcntl
15import glob
Jon Salz0405ab52012-03-16 15:26:52 +080016import logging
17import os
18import pickle
19import pipes
Jon Salz73e0fd02012-04-04 11:46:38 +080020import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080021import re
22import signal
23import subprocess
24import sys
25import tempfile
26import threading
27import time
28import traceback
Hung-Te Linf2f78f72012-02-08 19:27:11 +080029from collections import deque
30from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080031
32import factory_common
33from autotest_lib.client.bin import prespawner
34from autotest_lib.client.cros import factory
35from autotest_lib.client.cros.factory import state
36from autotest_lib.client.cros.factory import TestState
37from autotest_lib.client.cros.factory.event import Event
38from autotest_lib.client.cros.factory.event import EventClient
39from autotest_lib.client.cros.factory.event import EventServer
40
41
42SCRIPT_PATH = os.path.realpath(__file__)
43CROS_FACTORY_LIB_PATH = os.path.dirname(SCRIPT_PATH)
Hung-Te Lin6bb48552012-02-09 14:37:43 +080044FACTORY_UI_PATH = os.path.join(CROS_FACTORY_LIB_PATH, 'ui')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080045CLIENT_PATH = os.path.realpath(os.path.join(CROS_FACTORY_LIB_PATH, '..', '..'))
46DEFAULT_TEST_LIST_PATH = os.path.join(
47 CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
48HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
49
Jon Salz758e6cc2012-04-03 15:47:07 +080050GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
51You are running Goofy inside the chroot. Autotests are not supported.
52
53To use Goofy in the chroot, first install an Xvnc server:
54
55 sudo apt-get install tightvncserver
56
57...and then start a VNC X server outside the chroot:
58
59 vncserver :10 &
60 vncviewer :10
61
62...and run Goofy as follows:
63
64 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
65''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080066suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080067
68def get_hwid_cfg():
69 '''
70 Returns the HWID config tag, or an empty string if none can be found.
71 '''
72 if 'CROS_HWID' in os.environ:
73 return os.environ['CROS_HWID']
74 if os.path.exists(HWID_CFG_PATH):
75 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
76 return hwid_cfg_handle.read().strip()
77 return ''
78
79
80def find_test_list():
81 '''
82 Returns the path to the active test list, based on the HWID config tag.
83 '''
84 hwid_cfg = get_hwid_cfg()
85
86 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
87 if hwid_cfg:
88 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
89 if os.path.exists(test_list):
90 logging.info('Using special test list: %s', test_list)
91 return test_list
92 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
93
94 test_list = DEFAULT_TEST_LIST_PATH
95 if os.path.exists(test_list):
96 return test_list
97
98 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
99 if os.path.exists(test_list):
100 logging.info('Using default test list: ' + test_list)
101 return test_list
102 logging.info('ERROR: Cannot find any test list.')
103
Jon Salz73e0fd02012-04-04 11:46:38 +0800104# TODO(jsalz): Move utility functions (e.g., is_process_alive,
105# kill_process_tree, are_shift_keys_depressed) into a separate
106# utilities module.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800107
108def is_process_alive(pid):
109 '''
110 Returns true if the named process is alive and not a zombie.
111 '''
112 try:
113 with open("/proc/%d/stat" % pid) as f:
114 return f.readline().split()[2] != 'Z'
115 except IOError:
116 return False
117
118
119def kill_process_tree(process, caption):
120 '''
121 Kills a process and all its subprocesses.
122
123 @param process: The process to kill (opened with the subprocess module).
124 @param caption: A caption describing the process.
125 '''
Hung-Te Lin1b094702012-03-15 18:14:15 +0800126 # os.kill does not kill child processes. os.killpg kills all processes
127 # sharing same group (and is usually used for killing process tree). But in
128 # our case, to preserve PGID for autotest and upstart service, we need to
129 # iterate through each level until leaf of the tree.
130
131 def get_all_pids(root):
132 ps_output = subprocess.Popen(['ps','--no-headers','-eo','pid,ppid'],
133 stdout=subprocess.PIPE)
134 children = {}
135 for line in ps_output.stdout:
136 match = re.findall('\d+', line)
137 children.setdefault(int(match[1]), []).append(int(match[0]))
138 pids = []
139 def add_children(pid):
140 pids.append(pid)
141 map(add_children, children.get(pid, []))
142 add_children(root)
Hung-Te Lin02988092012-03-16 12:35:17 +0800143 # Reverse the list to first kill children then parents.
144 # Note reversed(pids) will return an iterator instead of real list, so
145 # we must explicitly call pids.reverse() here.
146 pids.reverse()
147 return pids
Hung-Te Lin1b094702012-03-15 18:14:15 +0800148
Hung-Te Lin02988092012-03-16 12:35:17 +0800149 pids = get_all_pids(process.pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800150 for sig in [signal.SIGTERM, signal.SIGKILL]:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800151 logging.info('Stopping %s (pid=%s)...', caption, sorted(pids))
152
153 for i in range(25): # Try 25 times (200 ms between tries)
154 for pid in pids:
155 try:
Hung-Te Lin02988092012-03-16 12:35:17 +0800156 logging.info("Sending signal %s to %d", sig, pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800157 os.kill(pid, sig)
158 except OSError:
159 pass
Jon Salza751ce52012-03-15 14:59:33 +0800160 pids = filter(is_process_alive, pids)
161 if not pids:
162 return
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800163 time.sleep(0.2) # Sleep 200 ms and try again
164
165 logging.warn('Failed to stop %s process. Ignoring.', caption)
166
167
Jon Salz73e0fd02012-04-04 11:46:38 +0800168def are_shift_keys_depressed():
169 '''Returns True if both shift keys are depressed.'''
170 # From #include <linux/input.h>
171 KEY_LEFTSHIFT = 42
172 KEY_RIGHTSHIFT = 54
173
174 for kbd in glob.glob("/dev/input/by-path/*kbd"):
175 f = os.open(kbd, os.O_RDONLY)
176 buf = array.array('b', [0] * 96)
177
178 # EVIOCGKEY (from #include <linux/input.h>)
179 fcntl.ioctl(f, 0x80604518, buf)
180
181 def is_pressed(key):
182 return (buf[key / 8] & (1 << (key % 8))) != 0
183
184 if is_pressed(KEY_LEFTSHIFT) and is_pressed(KEY_RIGHTSHIFT):
185 return True
186
187 return False
188
189
190class Environment(object):
191 '''
192 Abstract base class for external test operations, e.g., run an autotest,
193 shutdown, or reboot.
194
195 The Environment is assumed not to be thread-safe: callers must grab the lock
196 before calling any methods. This is primarily necessary because we mock out
197 this Environment with mox, and unfortunately mox is not thread-safe.
198 TODO(jsalz): Try to write a thread-safe wrapper for mox.
199 '''
200 lock = threading.Lock()
201
202 def shutdown(self, operation):
203 '''
204 Shuts the machine down (from a ShutdownStep).
205
206 Args:
207 operation: 'reboot' or 'halt'.
208
209 Returns:
210 True if Goofy should gracefully exit, or False if Goofy
211 should just consider the shutdown to have suceeded (e.g.,
212 in the chroot).
213 '''
214 raise NotImplementedError()
215
216
217 def spawn_autotest(self, name, args, env_additions, result_file):
218 '''
219 Spawns a process to run an autotest.
220
221 Args:
222 name: Name of the autotest to spawn.
223 args: Command-line arguments.
224 env_additions: Additions to the environment.
225 result_file: Expected location of the result file.
226 '''
227 raise NotImplementedError()
228
229
230class DUTEnvironment(Environment):
231 '''
232 A real environment on a device under test.
233 '''
234 def shutdown(self, operation):
235 assert operation in ['reboot', 'halt']
236 logging.info('Shutting down: %s', operation)
237 subprocess.check_call('sync')
238 subprocess.check_call(operation)
239 time.sleep(30)
240 assert False, 'Never reached (should %s)' % operation
241
242 def spawn_autotest(self, name, args, env_additions, result_file):
243 return prespawner.spawn(args, env_additions)
244
245
246class ChrootEnvironment(Environment):
247 '''
248 A chroot environment that doesn't actually shutdown or run autotests.
249 '''
250 def shutdown(self, operation):
251 assert operation in ['reboot', 'halt']
252 logging.warn('In chroot: skipping %s', operation)
253 return False
254
255 def spawn_autotest(self, name, args, env_additions, result_file):
256 logging.warn('In chroot: skipping autotest %s', name)
257 # Just mark it as passed
258 with open(result_file, 'w') as out:
259 pickle.dump((TestState.PASSED, 'Passed'), out)
260 # Start a process that will return with a true exit status in
261 # 2 seconds (just like a happy autotest).
262 return subprocess.Popen(['sleep', '2'])
263
264
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800265class TestInvocation(object):
266 '''
267 State for an active test.
268 '''
269 def __init__(self, goofy, test, on_completion=None):
270 '''Constructor.
271
272 @param goofy: The controlling Goofy object.
273 @param test: The FactoryTest object to test.
274 @param on_completion: Callback to invoke in the goofy event queue
275 on completion.
276 '''
277 self.goofy = goofy
278 self.test = test
279 self.thread = threading.Thread(target=self._run)
280 self.on_completion = on_completion
281
282 self._lock = threading.Lock()
283 # The following properties are guarded by the lock.
284 self._aborted = False
285 self._completed = False
286 self._process = None
287
Jon Salz73e0fd02012-04-04 11:46:38 +0800288 def __repr__(self):
289 return 'TestInvocation(_aborted=%s, _completed=%s)' % (
290 self._aborted, self._completed)
291
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800292 def start(self):
293 '''Starts the test thread.'''
294 self.thread.start()
295
296 def abort_and_join(self):
297 '''
298 Aborts a test (must be called from the event controller thread).
299 '''
300 with self._lock:
301 self._aborted = True
302 if self._process:
303 kill_process_tree(self._process, 'autotest')
304 if self.thread:
305 self.thread.join()
306 with self._lock:
307 # Should be set by the thread itself, but just in case...
308 self._completed = True
309
310 def is_completed(self):
311 '''
312 Returns true if the test has finished.
313 '''
314 with self._lock:
315 return self._completed
316
317 def _invoke_autotest(self, test, dargs):
318 '''
319 Invokes an autotest test.
320
321 This method encapsulates all the magic necessary to run a single
322 autotest test using the 'autotest' command-line tool and get a
323 sane pass/fail status and error message out. It may be better
324 to just write our own command-line wrapper for job.run_test
325 instead.
326
327 @param test: the autotest to run
328 @param dargs: the argument map
329 @return: tuple of status (TestState.PASSED or TestState.FAILED) and
330 error message, if any
331 '''
332 status = TestState.FAILED
333 error_msg = 'Unknown'
334
335 try:
336 client_dir = CLIENT_PATH
337
338 output_dir = '%s/results/%s' % (client_dir, test.path)
339 if not os.path.exists(output_dir):
340 os.makedirs(output_dir)
341 tmp_dir = tempfile.mkdtemp(prefix='tmp', dir=output_dir)
342
343 control_file = os.path.join(tmp_dir, 'control')
344 result_file = os.path.join(tmp_dir, 'result')
345 args_file = os.path.join(tmp_dir, 'args')
346
347 with open(args_file, 'w') as f:
348 pickle.dump(dargs, f)
349
350 # Create a new control file to use to run the test
351 with open(control_file, 'w') as f:
352 print >> f, 'import common, traceback, utils'
353 print >> f, 'import cPickle as pickle'
354 print >> f, ("success = job.run_test("
355 "'%s', **pickle.load(open('%s')))" % (
356 test.autotest_name, args_file))
357
358 print >> f, (
359 "pickle.dump((success, "
360 "str(job.last_error) if job.last_error else None), "
361 "open('%s', 'w'), protocol=2)"
362 % result_file)
363
364 args = ['%s/bin/autotest' % client_dir,
365 '--output_dir', output_dir,
366 control_file]
367
368 factory.console.info('Running test %s' % test.path)
369 logging.debug('Test command line: %s', ' '.join(
370 [pipes.quote(arg) for arg in args]))
371
372 with self._lock:
Jon Salz73e0fd02012-04-04 11:46:38 +0800373 with self.goofy.env.lock:
374 self._process = self.goofy.env.spawn_autotest(
375 test.autotest_name, args,
376 {'CROS_FACTORY_TEST_PATH': test.path}, result_file)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800377
378 returncode = self._process.wait()
379 with self._lock:
380 if self._aborted:
381 error_msg = 'Aborted by operator'
382 return
383
384 if returncode:
385 # Only happens when there is an autotest-level problem (not when
386 # the test actually failed).
387 error_msg = 'autotest returned with code %d' % returncode
388 return
389
390 with open(result_file) as f:
391 try:
392 success, error_msg = pickle.load(f)
393 except: # pylint: disable=W0702
394 logging.exception('Unable to retrieve autotest results')
395 error_msg = 'Unable to retrieve autotest results'
396 return
397
398 if success:
399 status = TestState.PASSED
400 error_msg = ''
401 except Exception: # pylint: disable=W0703
402 traceback.print_exc(sys.stderr)
Jon Salz73e0fd02012-04-04 11:46:38 +0800403 # Make sure Goofy reports the exception upon destruction
404 # (e.g., for testing)
405 self.goofy.record_exception(traceback.format_exception_only(
406 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800407 finally:
408 factory.console.info('Test %s: %s' % (test.path, status))
409 return status, error_msg # pylint: disable=W0150
410
411 def _run(self):
412 with self._lock:
413 if self._aborted:
414 return
415
416 count = self.test.update_state(
417 status=TestState.ACTIVE, increment_count=1, error_msg='').count
418
419 test_tag = '%s_%s' % (self.test.path, count)
420 dargs = dict(self.test.dargs)
421 dargs.update({'tag': test_tag,
Jon Salz73e0fd02012-04-04 11:46:38 +0800422 'test_list_path': self.goofy.options.test_list})
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800423
424 status, error_msg = self._invoke_autotest(self.test, dargs)
425 self.test.update_state(status=status, error_msg=error_msg,
426 visible=False)
427 with self._lock:
428 self._completed = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800429
Jon Salz73e0fd02012-04-04 11:46:38 +0800430 self.goofy.run_queue.put(self.goofy.reap_completed_tests)
431 if self.on_completion:
432 self.goofy.run_queue.put(self.on_completion)
433
434
435_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800436
437class Goofy(object):
438 '''
439 The main factory flow.
440
441 Note that all methods in this class must be invoked from the main
442 (event) thread. Other threads, such as callbacks and TestInvocation
443 methods, should instead post events on the run queue.
444
445 TODO: Unit tests. (chrome-os-partner:7409)
446
447 Properties:
448 state_instance: An instance of FactoryState.
449 state_server: The FactoryState XML/RPC server.
450 state_server_thread: A thread running state_server.
451 event_server: The EventServer socket server.
452 event_server_thread: A thread running event_server.
453 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800454 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800455 run_queue: A queue of callbacks to invoke from the main thread.
456 invocations: A map from FactoryTest objects to the corresponding
457 TestInvocations objects representing active tests.
458 tests_to_run: A deque of tests that should be run when the current
459 test(s) complete.
460 options: Command-line options.
461 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800462 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800463 event_handlers: Map of Event.Type to the method used to handle that
464 event. If the method has an 'event' argument, the event is passed
465 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800466 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800467 '''
468 def __init__(self):
469 self.state_instance = None
470 self.state_server = None
471 self.state_server_thread = None
472 self.event_server = None
473 self.event_server_thread = None
474 self.event_client = None
475 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800476 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800477 self.invocations = {}
478 self.tests_to_run = deque()
479 self.visible_test = None
480
481 self.options = None
482 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800483 self.test_list = None
484
Jon Salz0405ab52012-03-16 15:26:52 +0800485 self.event_handlers = {
486 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800487 Event.Type.SHOW_NEXT_ACTIVE_TEST:
488 lambda event: self.show_next_active_test(),
489 Event.Type.RESTART_TESTS:
490 lambda event: self.restart_tests(),
491 Event.Type.AUTO_RUN:
492 lambda event: self.auto_run(),
493 Event.Type.RE_RUN_FAILED:
494 lambda event: self.re_run_failed(),
495 Event.Type.REVIEW:
496 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800497 }
498
Jon Salz73e0fd02012-04-04 11:46:38 +0800499 self.exceptions = []
500
501 def destroy(self):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800502 if self.ui_process:
503 kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800504 self.ui_process = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800505 if self.state_server_thread:
506 logging.info('Stopping state server')
507 self.state_server.shutdown()
508 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800509 self.state_server.server_close()
510 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800511 if self.event_server_thread:
512 logging.info('Stopping event server')
513 self.event_server.shutdown() # pylint: disable=E1101
514 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800515 self.event_server.server_close()
516 self.event_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800517 prespawner.stop()
Jon Salz73e0fd02012-04-04 11:46:38 +0800518 self.check_exceptions()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800519
520 def start_state_server(self):
521 self.state_instance, self.state_server = state.create_server()
522 logging.info('Starting state server')
523 self.state_server_thread = threading.Thread(
524 target=self.state_server.serve_forever)
525 self.state_server_thread.start()
526
527 def start_event_server(self):
528 self.event_server = EventServer()
529 logging.info('Starting factory event server')
530 self.event_server_thread = threading.Thread(
531 target=self.event_server.serve_forever) # pylint: disable=E1101
532 self.event_server_thread.start()
533
534 self.event_client = EventClient(
535 callback=self.handle_event, event_loop=self.run_queue)
536
537 def start_ui(self):
Jon Salz73e0fd02012-04-04 11:46:38 +0800538 ui_proc_args = [FACTORY_UI_PATH, self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800539 if self.options.verbose:
540 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800541 logging.info('Starting ui %s', ui_proc_args)
542 self.ui_process = subprocess.Popen(ui_proc_args)
543 logging.info('Waiting for UI to come up...')
544 self.event_client.wait(
545 lambda event: event.type == Event.Type.UI_READY)
546 logging.info('UI has started')
547
548 def set_visible_test(self, test):
549 if self.visible_test == test:
550 return
551
552 if test:
553 test.update_state(visible=True)
554 if self.visible_test:
555 self.visible_test.update_state(visible=False)
556 self.visible_test = test
557
Jon Salz74ad3262012-03-16 14:40:55 +0800558 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800559 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800560 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800561
Jon Salz74ad3262012-03-16 14:40:55 +0800562 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800563 @param state: The test state.
564 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800565 state = test.update_state(increment_shutdown_count=1)
566 logging.info('Detected shutdown (%d of %d)',
567 state.shutdown_count, test.iterations)
568 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800569 # Good!
570 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800571 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800572 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800573 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800574 error_msg='Too many shutdowns')
Jon Salz73e0fd02012-04-04 11:46:38 +0800575 elif are_shift_keys_depressed():
576 logging.info('Shift keys are depressed; cancelling restarts')
577 # Abort shutdown
578 test.update_state(
579 status=TestState.FAILED,
580 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800581 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800582 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800583 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800584
585 def init_states(self):
586 '''
587 Initializes all states on startup.
588 '''
589 for test in self.test_list.get_all_tests():
590 # Make sure the state server knows about all the tests,
591 # defaulting to an untested state.
592 test.update_state(update_parent=False, visible=False)
593
594 # Any 'active' tests should be marked as failed now.
595 for test in self.test_list.walk():
596 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800597 if state.status != TestState.ACTIVE:
598 continue
599 if isinstance(test, factory.ShutdownStep):
600 # Shutdown while the test was active - that's good.
601 self.handle_shutdown_complete(test, state)
602 else:
603 test.update_state(status=TestState.FAILED,
604 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800605
606 def show_next_active_test(self):
607 '''
608 Rotates to the next visible active test.
609 '''
610 self.reap_completed_tests()
611 active_tests = [
612 t for t in self.test_list.walk()
613 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
614 if not active_tests:
615 return
616
617 try:
618 next_test = active_tests[
619 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
620 except ValueError: # visible_test not present in active_tests
621 next_test = active_tests[0]
622
623 self.set_visible_test(next_test)
624
625 def handle_event(self, event):
626 '''
627 Handles an event from the event server.
628 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800629 handler = self.event_handlers.get(event.type)
630 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800631 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800632 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800633 # We don't register handlers for all event types - just ignore
634 # this event.
635 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800636
637 def run_next_test(self):
638 '''
639 Runs the next eligible test (or tests) in self.tests_to_run.
640 '''
641 self.reap_completed_tests()
642 while self.tests_to_run:
643 logging.debug('Tests to run: %s',
644 [x.path for x in self.tests_to_run])
645
646 test = self.tests_to_run[0]
647
648 if test in self.invocations:
649 logging.info('Next test %s is already running', test.path)
650 self.tests_to_run.popleft()
651 return
652
653 if self.invocations and not (test.backgroundable and all(
654 [x.backgroundable for x in self.invocations])):
655 logging.debug('Waiting for non-backgroundable tests to '
656 'complete before running %s', test.path)
657 return
658
659 self.tests_to_run.popleft()
660
Jon Salz74ad3262012-03-16 14:40:55 +0800661 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800662 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800663 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800664 # Save pending test list in the state server
665 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800666 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800667 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800668
Jon Salz73e0fd02012-04-04 11:46:38 +0800669 with self.env.lock:
670 shutdown_result = self.env.shutdown(test.operation)
671 if shutdown_result:
672 # That's all, folks!
673 self.run_queue.put(None)
674 return
675 else:
676 # Just pass (e.g., in the chroot).
677 test.update_state(status=TestState.PASSED)
678 self.state_instance.set_shared_data(
679 'tests_after_shutdown', None)
680 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800681
682 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
683 self.invocations[test] = invoc
684 if self.visible_test is None and test.has_ui:
685 self.set_visible_test(test)
686 invoc.start()
687
Jon Salz0405ab52012-03-16 15:26:52 +0800688 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800689 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800690 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800691
692 The tests are run in order unless one fails (then stops).
693 Backgroundable tests are run simultaneously; when a foreground test is
694 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800695
696 @param subtrees: Node or nodes containing tests to run (may either be
697 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800698 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800699 if type(subtrees) != list:
700 subtrees = [subtrees]
701
702 # Nodes we've seen so far, to avoid duplicates.
703 seen = set()
704
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800705 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800706 for subtree in subtrees:
707 for test in subtree.walk():
708 if test in seen:
709 continue
710 seen.add(test)
711
712 if not test.is_leaf():
713 continue
714 if (untested_only and
715 test.get_state().status != TestState.UNTESTED):
716 continue
717 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800718 self.run_next_test()
719
720 def reap_completed_tests(self):
721 '''
722 Removes completed tests from the set of active tests.
723
724 Also updates the visible test if it was reaped.
725 '''
726 for t, v in dict(self.invocations).iteritems():
727 if v.is_completed():
728 del self.invocations[t]
729
730 if (self.visible_test is None or
731 self.visible_test not in self.invocations):
732 self.set_visible_test(None)
733 # Make the first running test, if any, the visible test
734 for t in self.test_list.walk():
735 if t in self.invocations:
736 self.set_visible_test(t)
737 break
738
Hung-Te Lin96632362012-03-20 21:14:18 +0800739 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800740 '''
741 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800742
743 @param abort: True to change state of killed tests to FAILED, False for
744 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800745 '''
746 self.reap_completed_tests()
747 for test, invoc in self.invocations.items():
748 factory.console.info('Killing active test %s...' % test.path)
749 invoc.abort_and_join()
750 factory.console.info('Killed %s' % test.path)
751 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800752 if not abort:
753 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800754 self.reap_completed_tests()
755
Hung-Te Lin96632362012-03-20 21:14:18 +0800756 def abort_active_tests(self):
757 self.kill_active_tests(True)
758
Jon Salz73e0fd02012-04-04 11:46:38 +0800759 def main(self):
760 self.init()
761 self.run()
762
763 def init(self, args=None, env=None):
764 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800765
766 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800767 args: A list of command-line arguments. Uses sys.argv if
768 args is None.
769 env: An Environment instance to use (or None to choose
770 ChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800771 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800772 parser = OptionParser()
773 parser.add_option('-v', '--verbose', dest='verbose',
774 action='store_true',
775 help='Enable debug logging')
776 parser.add_option('--print_test_list', dest='print_test_list',
777 metavar='FILE',
778 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800779 parser.add_option('--restart', dest='restart',
780 action='store_true',
781 help='Clear all test state')
Jon Salz73e0fd02012-04-04 11:46:38 +0800782 parser.add_option('--noui', dest='ui',
783 action='store_false', default=True,
784 help='Disable the UI')
785 parser.add_option('--test_list', dest='test_list',
786 metavar='FILE',
787 help='Use FILE as test list')
788 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800789
Jon Salz73e0fd02012-04-04 11:46:38 +0800790 global _inited_logging
791 if not _inited_logging:
792 factory.init_logging('goofy', verbose=self.options.verbose)
793 _inited_logging = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800794
Jon Salz73e0fd02012-04-04 11:46:38 +0800795 if (not suppress_chroot_warning and
796 factory.in_chroot() and
Jon Salz758e6cc2012-04-03 15:47:07 +0800797 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
798 # That's not going to work! Tell the user how to run
799 # this way.
800 logging.warn(GOOFY_IN_CHROOT_WARNING)
801 time.sleep(1)
802
Jon Salz73e0fd02012-04-04 11:46:38 +0800803 if env:
804 self.env = env
805 elif factory.in_chroot():
806 self.env = ChrootEnvironment()
807 logging.warn(
808 'Using chroot environment: will not actually run autotests')
809 else:
810 self.env = DUTEnvironment()
811
Jon Salz758e6cc2012-04-03 15:47:07 +0800812 if self.options.restart:
813 state.clear_state()
814
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800815 if self.options.print_test_list:
816 print (factory.read_test_list(self.options.print_test_list).
817 __repr__(recursive=True))
818 return
819
820 logging.info('Started')
821
822 self.start_state_server()
823 # Update HWID configuration tag.
824 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
825
Jon Salz73e0fd02012-04-04 11:46:38 +0800826 self.options.test_list = (self.options.test_list or find_test_list())
827 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800828 self.state_instance)
829 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
830
831 self.init_states()
832 self.start_event_server()
Jon Salz73e0fd02012-04-04 11:46:38 +0800833 if self.options.ui:
834 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800835 prespawner.start()
836
837 def state_change_callback(test, state):
838 self.event_client.post_event(
839 Event(Event.Type.STATE_CHANGE,
840 path=test.path, state=state))
841 self.test_list.state_change_callback = state_change_callback
842
843 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800844 tests_after_shutdown = self.state_instance.get_shared_data(
845 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800846 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800847 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800848
Jon Salz758e6cc2012-04-03 15:47:07 +0800849 if tests_after_shutdown is not None:
850 logging.info('Resuming tests after shutdown: %s',
851 tests_after_shutdown)
852 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800853 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800854 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800855 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800856 else:
Jon Salz73e0fd02012-04-04 11:46:38 +0800857 self.run_queue.put(
858 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800859
Jon Salz73e0fd02012-04-04 11:46:38 +0800860 def run(self):
861 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800862 # Process events forever.
Jon Salz73e0fd02012-04-04 11:46:38 +0800863 while self.run_once():
864 pass
865
866 def run_once(self):
867 '''Runs all items pending in the event loop.
868
869 Returns:
870 True to keep going or False to shut down.
871 '''
872 events = [self.run_queue.get()] # Get at least one event
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800873 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800874 try:
875 events.append(self.run_queue.get_nowait())
876 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800877 break
878
Jon Salz73e0fd02012-04-04 11:46:38 +0800879 for event in events:
880 if not event:
881 # Shutdown request.
882 self.run_queue.task_done()
883 return False
884
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800885 try:
886 event()
887 except Exception as e: # pylint: disable=W0703
888 logging.error('Error in event loop: %s', e)
889 traceback.print_exc(sys.stderr)
890 # But keep going
891 finally:
892 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800893 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800894
Jon Salz0405ab52012-03-16 15:26:52 +0800895 def run_tests_with_status(self, *statuses_to_run):
896 '''Runs all top-level tests with a particular status.
897
898 All active tests, plus any tests to re-run, are reset.
899 '''
900 tests_to_reset = []
901 tests_to_run = []
902
903 for test in self.test_list.get_top_level_tests():
904 status = test.get_state().status
905 if status == TestState.ACTIVE or status in statuses_to_run:
906 # Reset the test (later; we will need to abort
907 # all active tests first).
908 tests_to_reset.append(test)
909 if status in statuses_to_run:
910 tests_to_run.append(test)
911
912 self.abort_active_tests()
913
914 # Reset all statuses of the tests to run (in case any tests were active;
915 # we want them to be run again).
916 for test_to_reset in tests_to_reset:
917 for test in test_to_reset.walk():
918 test.update_state(status=TestState.UNTESTED)
919
920 self.run_tests(tests_to_run, untested_only=True)
921
922 def restart_tests(self):
923 '''Restarts all tests.'''
924 self.abort_active_tests()
925 for test in self.test_list.walk():
926 test.update_state(status=TestState.UNTESTED)
927 self.run_tests(self.test_list)
928
929 def auto_run(self):
930 '''"Auto-runs" tests that have not been run yet.'''
931 self.run_tests_with_status(TestState.UNTESTED, TestState.ACTIVE)
932
933 def re_run_failed(self):
934 '''Re-runs failed tests.'''
935 self.run_tests_with_status(TestState.FAILED)
936
Jon Salz968e90b2012-03-18 16:12:43 +0800937 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800938 '''Event handler for showing review information screen.
939
940 The information screene is rendered by main UI program (ui.py), so in
941 goofy we only need to kill all active tests, set them as untested, and
942 clear remaining tests.
943 '''
944 self.kill_active_tests(False)
945 self.run_tests([])
946
Jon Salz0405ab52012-03-16 15:26:52 +0800947 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800948 '''Switches to a particular test.
949
950 @param event: The SWITCH_TEST event.
951 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800952 test = self.test_list.lookup_path(event.path)
953 if test:
954 invoc = self.invocations.get(test)
955 if invoc and test.backgroundable:
956 # Already running: just bring to the front if it
957 # has a UI.
958 logging.info('Setting visible test to %s', test.path)
959 self.event_client.post_event(
960 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
Jon Salz968e90b2012-03-18 16:12:43 +0800961 return
Jon Salz0405ab52012-03-16 15:26:52 +0800962 self.abort_active_tests()
963 for t in test.walk():
964 t.update_state(status=TestState.UNTESTED)
965 self.run_tests(test)
966 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800967 logging.error('Unknown test %r', event.key)
Jon Salz0405ab52012-03-16 15:26:52 +0800968
Jon Salz73e0fd02012-04-04 11:46:38 +0800969 def wait(self):
970 '''Waits for all pending invocations.
971
972 Useful for testing.
973 '''
974 for k, v in self.invocations.iteritems():
975 logging.info('Waiting for %s to complete...', k)
976 v.thread.join()
977
978 def check_exceptions(self):
979 '''Raises an error if any exceptions have occurred in
980 invocation threads.'''
981 if self.exceptions:
982 raise RuntimeError('Exception in invocation thread: %r' %
983 self.exceptions)
984
985 def record_exception(self, msg):
986 '''Records an exception in an invocation thread.
987
988 An exception with the given message will be rethrown when
989 Goofy is destroyed.'''
990 self.exceptions.append(msg)
991
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800992
993if __name__ == '__main__':
994 Goofy().main()