blob: 2b7b63abcb3c882f811d3c7e72b3f7b0482c295c [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
Jon Salz8375c2e2012-04-04 15:22:24 +080033from autotest_lib.client.bin.prespawner import Prespawner
Hung-Te Linf2f78f72012-02-08 19:27:11 +080034from 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"):
Jon Salz8375c2e2012-04-04 15:22:24 +0800175 try:
176 f = os.open(kbd, os.O_RDONLY)
177 except OSError as e:
178 if factory.in_chroot():
179 # That's OK; we're just not root
180 continue
181 else:
182 raise
Jon Salz73e0fd02012-04-04 11:46:38 +0800183 buf = array.array('b', [0] * 96)
184
185 # EVIOCGKEY (from #include <linux/input.h>)
186 fcntl.ioctl(f, 0x80604518, buf)
187
188 def is_pressed(key):
189 return (buf[key / 8] & (1 << (key % 8))) != 0
190
191 if is_pressed(KEY_LEFTSHIFT) and is_pressed(KEY_RIGHTSHIFT):
192 return True
193
194 return False
195
196
197class Environment(object):
198 '''
199 Abstract base class for external test operations, e.g., run an autotest,
200 shutdown, or reboot.
201
202 The Environment is assumed not to be thread-safe: callers must grab the lock
203 before calling any methods. This is primarily necessary because we mock out
204 this Environment with mox, and unfortunately mox is not thread-safe.
205 TODO(jsalz): Try to write a thread-safe wrapper for mox.
206 '''
207 lock = threading.Lock()
208
209 def shutdown(self, operation):
210 '''
211 Shuts the machine down (from a ShutdownStep).
212
213 Args:
214 operation: 'reboot' or 'halt'.
215
216 Returns:
217 True if Goofy should gracefully exit, or False if Goofy
218 should just consider the shutdown to have suceeded (e.g.,
219 in the chroot).
220 '''
221 raise NotImplementedError()
222
223
224 def spawn_autotest(self, name, args, env_additions, result_file):
225 '''
226 Spawns a process to run an autotest.
227
228 Args:
229 name: Name of the autotest to spawn.
230 args: Command-line arguments.
231 env_additions: Additions to the environment.
232 result_file: Expected location of the result file.
233 '''
234 raise NotImplementedError()
235
236
237class DUTEnvironment(Environment):
238 '''
239 A real environment on a device under test.
240 '''
241 def shutdown(self, operation):
242 assert operation in ['reboot', 'halt']
243 logging.info('Shutting down: %s', operation)
244 subprocess.check_call('sync')
245 subprocess.check_call(operation)
246 time.sleep(30)
247 assert False, 'Never reached (should %s)' % operation
248
249 def spawn_autotest(self, name, args, env_additions, result_file):
Jon Salz8375c2e2012-04-04 15:22:24 +0800250 return self.goofy.prespawner.spawn(args, env_additions)
Jon Salz73e0fd02012-04-04 11:46:38 +0800251
252
253class ChrootEnvironment(Environment):
254 '''
255 A chroot environment that doesn't actually shutdown or run autotests.
256 '''
257 def shutdown(self, operation):
258 assert operation in ['reboot', 'halt']
259 logging.warn('In chroot: skipping %s', operation)
260 return False
261
262 def spawn_autotest(self, name, args, env_additions, result_file):
263 logging.warn('In chroot: skipping autotest %s', name)
264 # Just mark it as passed
265 with open(result_file, 'w') as out:
266 pickle.dump((TestState.PASSED, 'Passed'), out)
267 # Start a process that will return with a true exit status in
268 # 2 seconds (just like a happy autotest).
269 return subprocess.Popen(['sleep', '2'])
270
271
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800272class TestInvocation(object):
273 '''
274 State for an active test.
275 '''
276 def __init__(self, goofy, test, on_completion=None):
277 '''Constructor.
278
279 @param goofy: The controlling Goofy object.
280 @param test: The FactoryTest object to test.
281 @param on_completion: Callback to invoke in the goofy event queue
282 on completion.
283 '''
284 self.goofy = goofy
285 self.test = test
Jon Salz8375c2e2012-04-04 15:22:24 +0800286 self.thread = threading.Thread(target=self._run,
287 name='TestInvocation-%s' % test.path)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800288 self.on_completion = on_completion
289
290 self._lock = threading.Lock()
291 # The following properties are guarded by the lock.
292 self._aborted = False
293 self._completed = False
294 self._process = None
295
Jon Salz73e0fd02012-04-04 11:46:38 +0800296 def __repr__(self):
297 return 'TestInvocation(_aborted=%s, _completed=%s)' % (
298 self._aborted, self._completed)
299
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800300 def start(self):
301 '''Starts the test thread.'''
302 self.thread.start()
303
304 def abort_and_join(self):
305 '''
306 Aborts a test (must be called from the event controller thread).
307 '''
308 with self._lock:
309 self._aborted = True
310 if self._process:
311 kill_process_tree(self._process, 'autotest')
312 if self.thread:
313 self.thread.join()
314 with self._lock:
315 # Should be set by the thread itself, but just in case...
316 self._completed = True
317
318 def is_completed(self):
319 '''
320 Returns true if the test has finished.
321 '''
322 with self._lock:
323 return self._completed
324
325 def _invoke_autotest(self, test, dargs):
326 '''
327 Invokes an autotest test.
328
329 This method encapsulates all the magic necessary to run a single
330 autotest test using the 'autotest' command-line tool and get a
331 sane pass/fail status and error message out. It may be better
332 to just write our own command-line wrapper for job.run_test
333 instead.
334
335 @param test: the autotest to run
336 @param dargs: the argument map
337 @return: tuple of status (TestState.PASSED or TestState.FAILED) and
338 error message, if any
339 '''
340 status = TestState.FAILED
341 error_msg = 'Unknown'
342
343 try:
344 client_dir = CLIENT_PATH
345
346 output_dir = '%s/results/%s' % (client_dir, test.path)
347 if not os.path.exists(output_dir):
348 os.makedirs(output_dir)
349 tmp_dir = tempfile.mkdtemp(prefix='tmp', dir=output_dir)
350
351 control_file = os.path.join(tmp_dir, 'control')
352 result_file = os.path.join(tmp_dir, 'result')
353 args_file = os.path.join(tmp_dir, 'args')
354
355 with open(args_file, 'w') as f:
356 pickle.dump(dargs, f)
357
358 # Create a new control file to use to run the test
359 with open(control_file, 'w') as f:
360 print >> f, 'import common, traceback, utils'
361 print >> f, 'import cPickle as pickle'
362 print >> f, ("success = job.run_test("
363 "'%s', **pickle.load(open('%s')))" % (
364 test.autotest_name, args_file))
365
366 print >> f, (
367 "pickle.dump((success, "
368 "str(job.last_error) if job.last_error else None), "
369 "open('%s', 'w'), protocol=2)"
370 % result_file)
371
372 args = ['%s/bin/autotest' % client_dir,
373 '--output_dir', output_dir,
374 control_file]
375
376 factory.console.info('Running test %s' % test.path)
377 logging.debug('Test command line: %s', ' '.join(
378 [pipes.quote(arg) for arg in args]))
379
380 with self._lock:
Jon Salz73e0fd02012-04-04 11:46:38 +0800381 with self.goofy.env.lock:
382 self._process = self.goofy.env.spawn_autotest(
383 test.autotest_name, args,
384 {'CROS_FACTORY_TEST_PATH': test.path}, result_file)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800385
386 returncode = self._process.wait()
387 with self._lock:
388 if self._aborted:
389 error_msg = 'Aborted by operator'
390 return
391
392 if returncode:
393 # Only happens when there is an autotest-level problem (not when
394 # the test actually failed).
395 error_msg = 'autotest returned with code %d' % returncode
396 return
397
398 with open(result_file) as f:
399 try:
400 success, error_msg = pickle.load(f)
401 except: # pylint: disable=W0702
402 logging.exception('Unable to retrieve autotest results')
403 error_msg = 'Unable to retrieve autotest results'
404 return
405
406 if success:
407 status = TestState.PASSED
408 error_msg = ''
409 except Exception: # pylint: disable=W0703
410 traceback.print_exc(sys.stderr)
Jon Salz73e0fd02012-04-04 11:46:38 +0800411 # Make sure Goofy reports the exception upon destruction
412 # (e.g., for testing)
413 self.goofy.record_exception(traceback.format_exception_only(
414 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415 finally:
416 factory.console.info('Test %s: %s' % (test.path, status))
417 return status, error_msg # pylint: disable=W0150
418
419 def _run(self):
420 with self._lock:
421 if self._aborted:
422 return
423
424 count = self.test.update_state(
425 status=TestState.ACTIVE, increment_count=1, error_msg='').count
426
427 test_tag = '%s_%s' % (self.test.path, count)
428 dargs = dict(self.test.dargs)
429 dargs.update({'tag': test_tag,
Jon Salz73e0fd02012-04-04 11:46:38 +0800430 'test_list_path': self.goofy.options.test_list})
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800431
432 status, error_msg = self._invoke_autotest(self.test, dargs)
433 self.test.update_state(status=status, error_msg=error_msg,
434 visible=False)
435 with self._lock:
436 self._completed = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800437
Jon Salz73e0fd02012-04-04 11:46:38 +0800438 self.goofy.run_queue.put(self.goofy.reap_completed_tests)
439 if self.on_completion:
440 self.goofy.run_queue.put(self.on_completion)
441
442
443_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800444
445class Goofy(object):
446 '''
447 The main factory flow.
448
449 Note that all methods in this class must be invoked from the main
450 (event) thread. Other threads, such as callbacks and TestInvocation
451 methods, should instead post events on the run queue.
452
453 TODO: Unit tests. (chrome-os-partner:7409)
454
455 Properties:
456 state_instance: An instance of FactoryState.
457 state_server: The FactoryState XML/RPC server.
458 state_server_thread: A thread running state_server.
459 event_server: The EventServer socket server.
460 event_server_thread: A thread running event_server.
461 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800462 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800463 run_queue: A queue of callbacks to invoke from the main thread.
464 invocations: A map from FactoryTest objects to the corresponding
465 TestInvocations objects representing active tests.
466 tests_to_run: A deque of tests that should be run when the current
467 test(s) complete.
468 options: Command-line options.
469 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800470 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800471 event_handlers: Map of Event.Type to the method used to handle that
472 event. If the method has an 'event' argument, the event is passed
473 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800474 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800475 '''
476 def __init__(self):
477 self.state_instance = None
478 self.state_server = None
479 self.state_server_thread = None
480 self.event_server = None
481 self.event_server_thread = None
482 self.event_client = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800483 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800484 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800485 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800486 self.invocations = {}
487 self.tests_to_run = deque()
488 self.visible_test = None
489
490 self.options = None
491 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800492 self.test_list = None
493
Jon Salz0405ab52012-03-16 15:26:52 +0800494 self.event_handlers = {
495 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800496 Event.Type.SHOW_NEXT_ACTIVE_TEST:
497 lambda event: self.show_next_active_test(),
498 Event.Type.RESTART_TESTS:
499 lambda event: self.restart_tests(),
500 Event.Type.AUTO_RUN:
501 lambda event: self.auto_run(),
502 Event.Type.RE_RUN_FAILED:
503 lambda event: self.re_run_failed(),
504 Event.Type.REVIEW:
505 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800506 }
507
Jon Salz73e0fd02012-04-04 11:46:38 +0800508 self.exceptions = []
509
510 def destroy(self):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800511 if self.ui_process:
512 kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800513 self.ui_process = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800514 if self.state_server_thread:
515 logging.info('Stopping state server')
516 self.state_server.shutdown()
517 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800518 self.state_server.server_close()
519 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800520 if self.event_server_thread:
521 logging.info('Stopping event server')
522 self.event_server.shutdown() # pylint: disable=E1101
523 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800524 self.event_server.server_close()
525 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800526 if self.prespawner:
527 logging.info('Stopping prespawner')
528 self.prespawner.stop()
529 self.prespawner = None
530 if self.event_client:
531 self.event_client.close()
Jon Salz73e0fd02012-04-04 11:46:38 +0800532 self.check_exceptions()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800533
534 def start_state_server(self):
535 self.state_instance, self.state_server = state.create_server()
536 logging.info('Starting state server')
537 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800538 target=self.state_server.serve_forever,
539 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800540 self.state_server_thread.start()
541
542 def start_event_server(self):
543 self.event_server = EventServer()
544 logging.info('Starting factory event server')
545 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800546 target=self.event_server.serve_forever,
547 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800548 self.event_server_thread.start()
549
550 self.event_client = EventClient(
551 callback=self.handle_event, event_loop=self.run_queue)
552
553 def start_ui(self):
Jon Salz73e0fd02012-04-04 11:46:38 +0800554 ui_proc_args = [FACTORY_UI_PATH, self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800555 if self.options.verbose:
556 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800557 logging.info('Starting ui %s', ui_proc_args)
558 self.ui_process = subprocess.Popen(ui_proc_args)
559 logging.info('Waiting for UI to come up...')
560 self.event_client.wait(
561 lambda event: event.type == Event.Type.UI_READY)
562 logging.info('UI has started')
563
564 def set_visible_test(self, test):
565 if self.visible_test == test:
566 return
567
568 if test:
569 test.update_state(visible=True)
570 if self.visible_test:
571 self.visible_test.update_state(visible=False)
572 self.visible_test = test
573
Jon Salz74ad3262012-03-16 14:40:55 +0800574 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800575 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800576 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800577
Jon Salz74ad3262012-03-16 14:40:55 +0800578 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579 @param state: The test state.
580 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800581 state = test.update_state(increment_shutdown_count=1)
582 logging.info('Detected shutdown (%d of %d)',
583 state.shutdown_count, test.iterations)
584 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800585 # Good!
586 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800587 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800588 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800589 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800590 error_msg='Too many shutdowns')
Jon Salz73e0fd02012-04-04 11:46:38 +0800591 elif are_shift_keys_depressed():
592 logging.info('Shift keys are depressed; cancelling restarts')
593 # Abort shutdown
594 test.update_state(
595 status=TestState.FAILED,
596 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800597 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800598 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800599 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800600
601 def init_states(self):
602 '''
603 Initializes all states on startup.
604 '''
605 for test in self.test_list.get_all_tests():
606 # Make sure the state server knows about all the tests,
607 # defaulting to an untested state.
608 test.update_state(update_parent=False, visible=False)
609
610 # Any 'active' tests should be marked as failed now.
611 for test in self.test_list.walk():
612 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800613 if state.status != TestState.ACTIVE:
614 continue
615 if isinstance(test, factory.ShutdownStep):
616 # Shutdown while the test was active - that's good.
617 self.handle_shutdown_complete(test, state)
618 else:
619 test.update_state(status=TestState.FAILED,
620 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800621
622 def show_next_active_test(self):
623 '''
624 Rotates to the next visible active test.
625 '''
626 self.reap_completed_tests()
627 active_tests = [
628 t for t in self.test_list.walk()
629 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
630 if not active_tests:
631 return
632
633 try:
634 next_test = active_tests[
635 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
636 except ValueError: # visible_test not present in active_tests
637 next_test = active_tests[0]
638
639 self.set_visible_test(next_test)
640
641 def handle_event(self, event):
642 '''
643 Handles an event from the event server.
644 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800645 handler = self.event_handlers.get(event.type)
646 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800647 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800648 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800649 # We don't register handlers for all event types - just ignore
650 # this event.
651 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800652
653 def run_next_test(self):
654 '''
655 Runs the next eligible test (or tests) in self.tests_to_run.
656 '''
657 self.reap_completed_tests()
658 while self.tests_to_run:
659 logging.debug('Tests to run: %s',
660 [x.path for x in self.tests_to_run])
661
662 test = self.tests_to_run[0]
663
664 if test in self.invocations:
665 logging.info('Next test %s is already running', test.path)
666 self.tests_to_run.popleft()
667 return
668
669 if self.invocations and not (test.backgroundable and all(
670 [x.backgroundable for x in self.invocations])):
671 logging.debug('Waiting for non-backgroundable tests to '
672 'complete before running %s', test.path)
673 return
674
675 self.tests_to_run.popleft()
676
Jon Salz74ad3262012-03-16 14:40:55 +0800677 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800678 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800679 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800680 # Save pending test list in the state server
681 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800682 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800683 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800684
Jon Salz73e0fd02012-04-04 11:46:38 +0800685 with self.env.lock:
686 shutdown_result = self.env.shutdown(test.operation)
687 if shutdown_result:
688 # That's all, folks!
689 self.run_queue.put(None)
690 return
691 else:
692 # Just pass (e.g., in the chroot).
693 test.update_state(status=TestState.PASSED)
694 self.state_instance.set_shared_data(
695 'tests_after_shutdown', None)
696 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800697
698 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
699 self.invocations[test] = invoc
700 if self.visible_test is None and test.has_ui:
701 self.set_visible_test(test)
702 invoc.start()
703
Jon Salz0405ab52012-03-16 15:26:52 +0800704 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800705 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800706 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800707
708 The tests are run in order unless one fails (then stops).
709 Backgroundable tests are run simultaneously; when a foreground test is
710 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800711
712 @param subtrees: Node or nodes containing tests to run (may either be
713 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800714 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800715 if type(subtrees) != list:
716 subtrees = [subtrees]
717
718 # Nodes we've seen so far, to avoid duplicates.
719 seen = set()
720
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800721 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800722 for subtree in subtrees:
723 for test in subtree.walk():
724 if test in seen:
725 continue
726 seen.add(test)
727
728 if not test.is_leaf():
729 continue
730 if (untested_only and
731 test.get_state().status != TestState.UNTESTED):
732 continue
733 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800734 self.run_next_test()
735
736 def reap_completed_tests(self):
737 '''
738 Removes completed tests from the set of active tests.
739
740 Also updates the visible test if it was reaped.
741 '''
742 for t, v in dict(self.invocations).iteritems():
743 if v.is_completed():
744 del self.invocations[t]
745
746 if (self.visible_test is None or
747 self.visible_test not in self.invocations):
748 self.set_visible_test(None)
749 # Make the first running test, if any, the visible test
750 for t in self.test_list.walk():
751 if t in self.invocations:
752 self.set_visible_test(t)
753 break
754
Hung-Te Lin96632362012-03-20 21:14:18 +0800755 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800756 '''
757 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800758
759 @param abort: True to change state of killed tests to FAILED, False for
760 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800761 '''
762 self.reap_completed_tests()
763 for test, invoc in self.invocations.items():
764 factory.console.info('Killing active test %s...' % test.path)
765 invoc.abort_and_join()
766 factory.console.info('Killed %s' % test.path)
767 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800768 if not abort:
769 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800770 self.reap_completed_tests()
771
Hung-Te Lin96632362012-03-20 21:14:18 +0800772 def abort_active_tests(self):
773 self.kill_active_tests(True)
774
Jon Salz73e0fd02012-04-04 11:46:38 +0800775 def main(self):
776 self.init()
777 self.run()
778
779 def init(self, args=None, env=None):
780 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800781
782 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800783 args: A list of command-line arguments. Uses sys.argv if
784 args is None.
785 env: An Environment instance to use (or None to choose
786 ChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800787 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800788 parser = OptionParser()
789 parser.add_option('-v', '--verbose', dest='verbose',
790 action='store_true',
791 help='Enable debug logging')
792 parser.add_option('--print_test_list', dest='print_test_list',
793 metavar='FILE',
794 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800795 parser.add_option('--restart', dest='restart',
796 action='store_true',
797 help='Clear all test state')
Jon Salz73e0fd02012-04-04 11:46:38 +0800798 parser.add_option('--noui', dest='ui',
799 action='store_false', default=True,
800 help='Disable the UI')
801 parser.add_option('--test_list', dest='test_list',
802 metavar='FILE',
803 help='Use FILE as test list')
804 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800805
Jon Salz73e0fd02012-04-04 11:46:38 +0800806 global _inited_logging
807 if not _inited_logging:
808 factory.init_logging('goofy', verbose=self.options.verbose)
809 _inited_logging = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800810
Jon Salz73e0fd02012-04-04 11:46:38 +0800811 if (not suppress_chroot_warning and
812 factory.in_chroot() and
Jon Salz758e6cc2012-04-03 15:47:07 +0800813 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
814 # That's not going to work! Tell the user how to run
815 # this way.
816 logging.warn(GOOFY_IN_CHROOT_WARNING)
817 time.sleep(1)
818
Jon Salz73e0fd02012-04-04 11:46:38 +0800819 if env:
820 self.env = env
821 elif factory.in_chroot():
822 self.env = ChrootEnvironment()
823 logging.warn(
824 'Using chroot environment: will not actually run autotests')
825 else:
826 self.env = DUTEnvironment()
827
Jon Salz758e6cc2012-04-03 15:47:07 +0800828 if self.options.restart:
829 state.clear_state()
830
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800831 if self.options.print_test_list:
832 print (factory.read_test_list(self.options.print_test_list).
833 __repr__(recursive=True))
834 return
835
836 logging.info('Started')
837
838 self.start_state_server()
839 # Update HWID configuration tag.
840 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
841
Jon Salz73e0fd02012-04-04 11:46:38 +0800842 self.options.test_list = (self.options.test_list or find_test_list())
843 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800844 self.state_instance)
845 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
846
847 self.init_states()
848 self.start_event_server()
Jon Salz73e0fd02012-04-04 11:46:38 +0800849 if self.options.ui:
850 self.start_ui()
Jon Salz8375c2e2012-04-04 15:22:24 +0800851 self.prespawner = Prespawner()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800852
853 def state_change_callback(test, state):
854 self.event_client.post_event(
855 Event(Event.Type.STATE_CHANGE,
856 path=test.path, state=state))
857 self.test_list.state_change_callback = state_change_callback
858
859 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800860 tests_after_shutdown = self.state_instance.get_shared_data(
861 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800862 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800863 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800864
Jon Salz758e6cc2012-04-03 15:47:07 +0800865 if tests_after_shutdown is not None:
866 logging.info('Resuming tests after shutdown: %s',
867 tests_after_shutdown)
868 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800869 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800870 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800871 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800872 else:
Jon Salz73e0fd02012-04-04 11:46:38 +0800873 self.run_queue.put(
874 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800875
Jon Salz73e0fd02012-04-04 11:46:38 +0800876 def run(self):
877 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800878 # Process events forever.
Jon Salz73e0fd02012-04-04 11:46:38 +0800879 while self.run_once():
880 pass
881
882 def run_once(self):
883 '''Runs all items pending in the event loop.
884
885 Returns:
886 True to keep going or False to shut down.
887 '''
888 events = [self.run_queue.get()] # Get at least one event
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800889 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800890 try:
891 events.append(self.run_queue.get_nowait())
892 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800893 break
894
Jon Salz73e0fd02012-04-04 11:46:38 +0800895 for event in events:
896 if not event:
897 # Shutdown request.
898 self.run_queue.task_done()
899 return False
900
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800901 try:
902 event()
903 except Exception as e: # pylint: disable=W0703
904 logging.error('Error in event loop: %s', e)
905 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800906 self.record_exception(traceback.format_exception_only(
907 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800908 # But keep going
909 finally:
910 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800911 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800912
Jon Salz0405ab52012-03-16 15:26:52 +0800913 def run_tests_with_status(self, *statuses_to_run):
914 '''Runs all top-level tests with a particular status.
915
916 All active tests, plus any tests to re-run, are reset.
917 '''
918 tests_to_reset = []
919 tests_to_run = []
920
921 for test in self.test_list.get_top_level_tests():
922 status = test.get_state().status
923 if status == TestState.ACTIVE or status in statuses_to_run:
924 # Reset the test (later; we will need to abort
925 # all active tests first).
926 tests_to_reset.append(test)
927 if status in statuses_to_run:
928 tests_to_run.append(test)
929
930 self.abort_active_tests()
931
932 # Reset all statuses of the tests to run (in case any tests were active;
933 # we want them to be run again).
934 for test_to_reset in tests_to_reset:
935 for test in test_to_reset.walk():
936 test.update_state(status=TestState.UNTESTED)
937
938 self.run_tests(tests_to_run, untested_only=True)
939
940 def restart_tests(self):
941 '''Restarts all tests.'''
942 self.abort_active_tests()
943 for test in self.test_list.walk():
944 test.update_state(status=TestState.UNTESTED)
945 self.run_tests(self.test_list)
946
947 def auto_run(self):
948 '''"Auto-runs" tests that have not been run yet.'''
949 self.run_tests_with_status(TestState.UNTESTED, TestState.ACTIVE)
950
951 def re_run_failed(self):
952 '''Re-runs failed tests.'''
953 self.run_tests_with_status(TestState.FAILED)
954
Jon Salz968e90b2012-03-18 16:12:43 +0800955 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800956 '''Event handler for showing review information screen.
957
958 The information screene is rendered by main UI program (ui.py), so in
959 goofy we only need to kill all active tests, set them as untested, and
960 clear remaining tests.
961 '''
962 self.kill_active_tests(False)
963 self.run_tests([])
964
Jon Salz0405ab52012-03-16 15:26:52 +0800965 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800966 '''Switches to a particular test.
967
968 @param event: The SWITCH_TEST event.
969 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800970 test = self.test_list.lookup_path(event.path)
971 if test:
972 invoc = self.invocations.get(test)
973 if invoc and test.backgroundable:
974 # Already running: just bring to the front if it
975 # has a UI.
976 logging.info('Setting visible test to %s', test.path)
977 self.event_client.post_event(
978 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
Jon Salz968e90b2012-03-18 16:12:43 +0800979 return
Jon Salz0405ab52012-03-16 15:26:52 +0800980 self.abort_active_tests()
981 for t in test.walk():
982 t.update_state(status=TestState.UNTESTED)
983 self.run_tests(test)
984 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800985 logging.error('Unknown test %r', event.key)
Jon Salz0405ab52012-03-16 15:26:52 +0800986
Jon Salz73e0fd02012-04-04 11:46:38 +0800987 def wait(self):
988 '''Waits for all pending invocations.
989
990 Useful for testing.
991 '''
992 for k, v in self.invocations.iteritems():
993 logging.info('Waiting for %s to complete...', k)
994 v.thread.join()
995
996 def check_exceptions(self):
997 '''Raises an error if any exceptions have occurred in
998 invocation threads.'''
999 if self.exceptions:
1000 raise RuntimeError('Exception in invocation thread: %r' %
1001 self.exceptions)
1002
1003 def record_exception(self, msg):
1004 '''Records an exception in an invocation thread.
1005
1006 An exception with the given message will be rethrown when
1007 Goofy is destroyed.'''
1008 self.exceptions.append(msg)
1009
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001010
1011if __name__ == '__main__':
1012 Goofy().main()