blob: 6f93389c2d188fc20f2d099bd23939dac8f541b9 [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
Jon Salz37eccbd2012-05-25 16:06:52 +08005# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08006# Use of this source code is governed by a BSD-style license that can be
7# found in the LICENSE file.
8
9'''
10The main factory flow that runs the factory test and finalizes a device.
11'''
12
Jon Salz0405ab52012-03-16 15:26:52 +080013import logging
14import os
Jon Salz73e0fd02012-04-04 11:46:38 +080015import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080016import subprocess
17import sys
Jon Salz0405ab52012-03-16 15:26:52 +080018import threading
19import time
20import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080021import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080022from collections import deque
23from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080024
Jon Salz0697cbf2012-07-04 15:14:04 +080025import factory_common # pylint: disable=W0611
Jon Salz83591782012-06-26 11:09:58 +080026from cros.factory.goofy.prespawner import Prespawner
27from cros.factory.test import factory
28from cros.factory.test import state
29from cros.factory.test.factory import TestState
30from cros.factory.goofy import updater
Jon Salz51528e12012-07-02 18:54:45 +080031from cros.factory.goofy import test_steps
32from cros.factory.goofy.event_log_watcher import EventLogWatcher
33from cros.factory.test import shopfloor
Jon Salz83591782012-06-26 11:09:58 +080034from cros.factory.test import utils
35from cros.factory.test.event import Event
36from cros.factory.test.event import EventClient
37from cros.factory.test.event import EventServer
38from cros.factory.event_log import EventLog
39from cros.factory.goofy.invocation import TestInvocation
40from cros.factory.goofy import system
41from cros.factory.goofy import test_environment
42from cros.factory.goofy.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080043
44
Jon Salz2f757d42012-06-27 17:06:42 +080045DEFAULT_TEST_LISTS_DIR = os.path.join(factory.FACTORY_PATH, 'test_lists')
46CUSTOM_DIR = os.path.join(factory.FACTORY_PATH, 'custom')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080047HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
48
Jon Salz8796e362012-05-24 11:39:09 +080049# File that suppresses reboot if present (e.g., for development).
50NO_REBOOT_FILE = '/var/log/factory.noreboot'
51
Jon Salz758e6cc2012-04-03 15:47:07 +080052GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
53You are running Goofy inside the chroot. Autotests are not supported.
54
55To use Goofy in the chroot, first install an Xvnc server:
56
Jon Salz0697cbf2012-07-04 15:14:04 +080057 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080058
59...and then start a VNC X server outside the chroot:
60
Jon Salz0697cbf2012-07-04 15:14:04 +080061 vncserver :10 &
62 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080063
64...and run Goofy as follows:
65
Jon Salz0697cbf2012-07-04 15:14:04 +080066 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080067''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080068suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080069
70def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080071 '''
72 Returns the HWID config tag, or an empty string if none can be found.
73 '''
74 if 'CROS_HWID' in os.environ:
75 return os.environ['CROS_HWID']
76 if os.path.exists(HWID_CFG_PATH):
77 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
78 return hwid_cfg_handle.read().strip()
79 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080080
81
82def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080083 '''
84 Returns the path to the active test list, based on the HWID config tag.
85 '''
86 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080087
Jon Salz0697cbf2012-07-04 15:14:04 +080088 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080089
Jon Salz0697cbf2012-07-04 15:14:04 +080090 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
91 search_files = ['test_list', 'test_list.all']
92 if hwid_cfg:
93 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080094
Jon Salz0697cbf2012-07-04 15:14:04 +080095 for d in search_dirs:
96 for f in search_files:
97 test_list = os.path.join(d, f)
98 if os.path.exists(test_list):
99 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800100
Jon Salz0697cbf2012-07-04 15:14:04 +0800101 logging.warn('Cannot find test lists named any of %s in any of %s',
102 search_files, search_dirs)
103 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800104
Jon Salz73e0fd02012-04-04 11:46:38 +0800105_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800106
107class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800108 '''
109 The main factory flow.
110
111 Note that all methods in this class must be invoked from the main
112 (event) thread. Other threads, such as callbacks and TestInvocation
113 methods, should instead post events on the run queue.
114
115 TODO: Unit tests. (chrome-os-partner:7409)
116
117 Properties:
118 uuid: A unique UUID for this invocation of Goofy.
119 state_instance: An instance of FactoryState.
120 state_server: The FactoryState XML/RPC server.
121 state_server_thread: A thread running state_server.
122 event_server: The EventServer socket server.
123 event_server_thread: A thread running event_server.
124 event_client: A client to the event server.
125 connection_manager: The connection_manager object.
126 network_enabled: Whether the connection_manager is currently
127 enabling connections.
128 ui_process: The factory ui process object.
129 run_queue: A queue of callbacks to invoke from the main thread.
130 invocations: A map from FactoryTest objects to the corresponding
131 TestInvocations objects representing active tests.
132 tests_to_run: A deque of tests that should be run when the current
133 test(s) complete.
134 options: Command-line options.
135 args: Command-line args.
136 test_list: The test list.
137 event_handlers: Map of Event.Type to the method used to handle that
138 event. If the method has an 'event' argument, the event is passed
139 to the handler.
140 exceptions: Exceptions encountered in invocation threads.
141 '''
142 def __init__(self):
143 self.uuid = str(uuid.uuid4())
144 self.state_instance = None
145 self.state_server = None
146 self.state_server_thread = None
147 self.event_server = None
148 self.event_server_thread = None
149 self.event_client = None
150 self.connection_manager = None
151 self.log_watcher = None
152 self.network_enabled = True
153 self.event_log = None
154 self.prespawner = None
155 self.ui_process = None
156 self.run_queue = Queue.Queue()
157 self.invocations = {}
158 self.tests_to_run = deque()
159 self.visible_test = None
160 self.chrome = None
161
162 self.options = None
163 self.args = None
164 self.test_list = None
165 self.on_ui_startup = []
166 self.env = None
167 self.last_shutdown_time = None
168
Jon Salz85a39882012-07-05 16:45:04 +0800169 def test_or_root(event, parent_or_group=True):
170 '''Returns the test affected by a particular event.
171
172 Args:
173 event: The event containing an optional 'path' attribute.
174 parent_on_group: If True, returns the top-level parent for a test (the
175 root node of the tests that need to be run together if the given test
176 path is to be run).
177 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800178 try:
179 path = event.path
180 except AttributeError:
181 path = None
182
183 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800184 test = self.test_list.lookup_path(path)
185 if parent_or_group:
186 test = test.get_top_level_parent_or_group()
187 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800188 else:
189 return self.test_list
190
191 self.event_handlers = {
192 Event.Type.SWITCH_TEST: self.handle_switch_test,
193 Event.Type.SHOW_NEXT_ACTIVE_TEST:
194 lambda event: self.show_next_active_test(),
195 Event.Type.RESTART_TESTS:
196 lambda event: self.restart_tests(root=test_or_root(event)),
197 Event.Type.AUTO_RUN:
198 lambda event: self.auto_run(root=test_or_root(event)),
199 Event.Type.RE_RUN_FAILED:
200 lambda event: self.re_run_failed(root=test_or_root(event)),
201 Event.Type.RUN_TESTS_WITH_STATUS:
202 lambda event: self.run_tests_with_status(
203 event.status,
204 root=test_or_root(event)),
205 Event.Type.REVIEW:
206 lambda event: self.show_review_information(),
207 Event.Type.UPDATE_SYSTEM_INFO:
208 lambda event: self.update_system_info(),
209 Event.Type.UPDATE_FACTORY:
210 lambda event: self.update_factory(),
211 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800212 lambda event: self.stop(root=test_or_root(event, False),
213 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800214 Event.Type.SET_VISIBLE_TEST:
215 lambda event: self.set_visible_test(
216 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800217 }
218
219 self.exceptions = []
220 self.web_socket_manager = None
221
222 def destroy(self):
223 if self.chrome:
224 self.chrome.kill()
225 self.chrome = None
226 if self.ui_process:
227 utils.kill_process_tree(self.ui_process, 'ui')
228 self.ui_process = None
229 if self.web_socket_manager:
230 logging.info('Stopping web sockets')
231 self.web_socket_manager.close()
232 self.web_socket_manager = None
233 if self.state_server_thread:
234 logging.info('Stopping state server')
235 self.state_server.shutdown()
236 self.state_server_thread.join()
237 self.state_server.server_close()
238 self.state_server_thread = None
239 if self.state_instance:
240 self.state_instance.close()
241 if self.event_server_thread:
242 logging.info('Stopping event server')
243 self.event_server.shutdown() # pylint: disable=E1101
244 self.event_server_thread.join()
245 self.event_server.server_close()
246 self.event_server_thread = None
247 if self.log_watcher:
248 if self.log_watcher.IsThreadStarted():
249 self.log_watcher.StopWatchThread()
250 self.log_watcher = None
251 if self.prespawner:
252 logging.info('Stopping prespawner')
253 self.prespawner.stop()
254 self.prespawner = None
255 if self.event_client:
256 logging.info('Closing event client')
257 self.event_client.close()
258 self.event_client = None
259 if self.event_log:
260 self.event_log.Close()
261 self.event_log = None
262 self.check_exceptions()
263 logging.info('Done destroying Goofy')
264
265 def start_state_server(self):
266 self.state_instance, self.state_server = (
267 state.create_server(bind_address='0.0.0.0'))
268 logging.info('Starting state server')
269 self.state_server_thread = threading.Thread(
270 target=self.state_server.serve_forever,
271 name='StateServer')
272 self.state_server_thread.start()
273
274 def start_event_server(self):
275 self.event_server = EventServer()
276 logging.info('Starting factory event server')
277 self.event_server_thread = threading.Thread(
278 target=self.event_server.serve_forever,
279 name='EventServer') # pylint: disable=E1101
280 self.event_server_thread.start()
281
282 self.event_client = EventClient(
283 callback=self.handle_event, event_loop=self.run_queue)
284
285 self.web_socket_manager = WebSocketManager(self.uuid)
286 self.state_server.add_handler("/event",
287 self.web_socket_manager.handle_web_socket)
288
289 def start_ui(self):
290 ui_proc_args = [
291 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
292 self.options.test_list]
293 if self.options.verbose:
294 ui_proc_args.append('-v')
295 logging.info('Starting ui %s', ui_proc_args)
296 self.ui_process = subprocess.Popen(ui_proc_args)
297 logging.info('Waiting for UI to come up...')
298 self.event_client.wait(
299 lambda event: event.type == Event.Type.UI_READY)
300 logging.info('UI has started')
301
302 def set_visible_test(self, test):
303 if self.visible_test == test:
304 return
305
306 if test:
307 test.update_state(visible=True)
308 if self.visible_test:
309 self.visible_test.update_state(visible=False)
310 self.visible_test = test
311
312 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800313 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800314 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800315
Jon Salz0697cbf2012-07-04 15:14:04 +0800316 @param test: The ShutdownStep.
317 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800318 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800319 test_state = test.update_state(increment_shutdown_count=1)
320 logging.info('Detected shutdown (%d of %d)',
321 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800322
Jon Salz0697cbf2012-07-04 15:14:04 +0800323 def log_and_update_state(status, error_msg, **kw):
324 self.event_log.Log('rebooted',
325 status=status, error_msg=error_msg, **kw)
326 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800327
Jon Salz0697cbf2012-07-04 15:14:04 +0800328 if not self.last_shutdown_time:
329 log_and_update_state(status=TestState.FAILED,
330 error_msg='Unable to read shutdown_time')
331 return
Jon Salz258a40c2012-04-19 12:34:01 +0800332
Jon Salz0697cbf2012-07-04 15:14:04 +0800333 now = time.time()
334 logging.info('%.03f s passed since reboot',
335 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800336
Jon Salz0697cbf2012-07-04 15:14:04 +0800337 if self.last_shutdown_time > now:
338 test.update_state(status=TestState.FAILED,
339 error_msg='Time moved backward during reboot')
340 elif (isinstance(test, factory.RebootStep) and
341 self.test_list.options.max_reboot_time_secs and
342 (now - self.last_shutdown_time >
343 self.test_list.options.max_reboot_time_secs)):
344 # A reboot took too long; fail. (We don't check this for
345 # HaltSteps, because the machine could be halted for a
346 # very long time, and even unplugged with battery backup,
347 # thus hosing the clock.)
348 log_and_update_state(
349 status=TestState.FAILED,
350 error_msg=('More than %d s elapsed during reboot '
351 '(%.03f s, from %s to %s)' % (
352 self.test_list.options.max_reboot_time_secs,
353 now - self.last_shutdown_time,
354 utils.TimeString(self.last_shutdown_time),
355 utils.TimeString(now))),
356 duration=(now-self.last_shutdown_time))
357 elif test_state.shutdown_count == test.iterations:
358 # Good!
359 log_and_update_state(status=TestState.PASSED,
360 duration=(now - self.last_shutdown_time),
361 error_msg='')
362 elif test_state.shutdown_count > test.iterations:
363 # Shut down too many times
364 log_and_update_state(status=TestState.FAILED,
365 error_msg='Too many shutdowns')
366 elif utils.are_shift_keys_depressed():
367 logging.info('Shift keys are depressed; cancelling restarts')
368 # Abort shutdown
369 log_and_update_state(
370 status=TestState.FAILED,
371 error_msg='Shutdown aborted with double shift keys')
372 else:
373 def handler():
374 if self._prompt_cancel_shutdown(
375 test, test_state.shutdown_count + 1):
376 log_and_update_state(
377 status=TestState.FAILED,
378 error_msg='Shutdown aborted by operator')
379 return
Jon Salz0405ab52012-03-16 15:26:52 +0800380
Jon Salz0697cbf2012-07-04 15:14:04 +0800381 # Time to shutdown again
382 log_and_update_state(
383 status=TestState.ACTIVE,
384 error_msg='',
385 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800386
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 self.event_log.Log('shutdown', operation='reboot')
388 self.state_instance.set_shared_data('shutdown_time',
389 time.time())
390 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800391
Jon Salz0697cbf2012-07-04 15:14:04 +0800392 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800393
Jon Salz0697cbf2012-07-04 15:14:04 +0800394 def _prompt_cancel_shutdown(self, test, iteration):
395 if self.options.ui != 'chrome':
396 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800397
Jon Salz0697cbf2012-07-04 15:14:04 +0800398 pending_shutdown_data = {
399 'delay_secs': test.delay_secs,
400 'time': time.time() + test.delay_secs,
401 'operation': test.operation,
402 'iteration': iteration,
403 'iterations': test.iterations,
404 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800405
Jon Salz0697cbf2012-07-04 15:14:04 +0800406 # Create a new (threaded) event client since we
407 # don't want to use the event loop for this.
408 with EventClient() as event_client:
409 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
410 **pending_shutdown_data))
411 aborted = event_client.wait(
412 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
413 timeout=test.delay_secs) is not None
414 if aborted:
415 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
416 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800417
Jon Salz0697cbf2012-07-04 15:14:04 +0800418 def init_states(self):
419 '''
420 Initializes all states on startup.
421 '''
422 for test in self.test_list.get_all_tests():
423 # Make sure the state server knows about all the tests,
424 # defaulting to an untested state.
425 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800426
Jon Salz0697cbf2012-07-04 15:14:04 +0800427 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800428
Jon Salz0697cbf2012-07-04 15:14:04 +0800429 # Any 'active' tests should be marked as failed now.
430 for test in self.test_list.walk():
431 test_state = test.get_state()
432 if test_state.status != TestState.ACTIVE:
433 continue
434 if isinstance(test, factory.ShutdownStep):
435 # Shutdown while the test was active - that's good.
436 self.handle_shutdown_complete(test, test_state)
437 else:
438 # Unexpected shutdown. Grab /var/log/messages for context.
439 if var_log_messages is None:
440 try:
441 var_log_messages = (
442 utils.var_log_messages_before_reboot())
443 # Write it to the log, to make it easier to
444 # correlate with /var/log/messages.
445 logging.info(
446 'Unexpected shutdown. '
447 'Tail of /var/log/messages before last reboot:\n'
448 '%s', ('\n'.join(
449 ' ' + x for x in var_log_messages)))
450 except: # pylint: disable=W0702
451 logging.exception('Unable to grok /var/log/messages')
452 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800453
Jon Salz0697cbf2012-07-04 15:14:04 +0800454 error_msg = 'Unexpected shutdown while test was running'
455 self.event_log.Log('end_test',
456 path=test.path,
457 status=TestState.FAILED,
458 invocation=test.get_state().invocation,
459 error_msg=error_msg,
460 var_log_messages='\n'.join(var_log_messages))
461 test.update_state(
462 status=TestState.FAILED,
463 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800464
Jon Salz0697cbf2012-07-04 15:14:04 +0800465 def show_next_active_test(self):
466 '''
467 Rotates to the next visible active test.
468 '''
469 self.reap_completed_tests()
470 active_tests = [
471 t for t in self.test_list.walk()
472 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
473 if not active_tests:
474 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800475
Jon Salz0697cbf2012-07-04 15:14:04 +0800476 try:
477 next_test = active_tests[
478 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
479 except ValueError: # visible_test not present in active_tests
480 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800481
Jon Salz0697cbf2012-07-04 15:14:04 +0800482 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800483
Jon Salz0697cbf2012-07-04 15:14:04 +0800484 def handle_event(self, event):
485 '''
486 Handles an event from the event server.
487 '''
488 handler = self.event_handlers.get(event.type)
489 if handler:
490 handler(event)
491 else:
492 # We don't register handlers for all event types - just ignore
493 # this event.
494 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800495
Jon Salz0697cbf2012-07-04 15:14:04 +0800496 def run_next_test(self):
497 '''
498 Runs the next eligible test (or tests) in self.tests_to_run.
499 '''
500 self.reap_completed_tests()
501 while self.tests_to_run:
502 logging.debug('Tests to run: %s',
503 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800504
Jon Salz0697cbf2012-07-04 15:14:04 +0800505 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800506
Jon Salz0697cbf2012-07-04 15:14:04 +0800507 if test in self.invocations:
508 logging.info('Next test %s is already running', test.path)
509 self.tests_to_run.popleft()
510 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800511
Jon Salz0697cbf2012-07-04 15:14:04 +0800512 if self.invocations and not (test.backgroundable and all(
513 [x.backgroundable for x in self.invocations])):
514 logging.debug('Waiting for non-backgroundable tests to '
515 'complete before running %s', test.path)
516 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800517
Jon Salz0697cbf2012-07-04 15:14:04 +0800518 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800519
Jon Salz0697cbf2012-07-04 15:14:04 +0800520 if isinstance(test, factory.ShutdownStep):
521 if os.path.exists(NO_REBOOT_FILE):
522 test.update_state(
523 status=TestState.FAILED, increment_count=1,
524 error_msg=('Skipped shutdown since %s is present' %
525 NO_REBOOT_FILE))
526 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800527
Jon Salz0697cbf2012-07-04 15:14:04 +0800528 test.update_state(status=TestState.ACTIVE, increment_count=1,
529 error_msg='', shutdown_count=0)
530 if self._prompt_cancel_shutdown(test, 1):
531 self.event_log.Log('reboot_cancelled')
532 test.update_state(
533 status=TestState.FAILED, increment_count=1,
534 error_msg='Shutdown aborted by operator',
535 shutdown_count=0)
536 return
Jon Salz2f757d42012-06-27 17:06:42 +0800537
Jon Salz0697cbf2012-07-04 15:14:04 +0800538 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800539 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800540 'tests_after_shutdown',
541 [t.path for t in self.tests_to_run])
542 # Save shutdown time
543 self.state_instance.set_shared_data('shutdown_time',
544 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800545
Jon Salz0697cbf2012-07-04 15:14:04 +0800546 with self.env.lock:
547 self.event_log.Log('shutdown', operation=test.operation)
548 shutdown_result = self.env.shutdown(test.operation)
549 if shutdown_result:
550 # That's all, folks!
551 self.run_queue.put(None)
552 return
553 else:
554 # Just pass (e.g., in the chroot).
555 test.update_state(status=TestState.PASSED)
556 self.state_instance.set_shared_data(
557 'tests_after_shutdown', None)
558 # Send event with no fields to indicate that there is no
559 # longer a pending shutdown.
560 self.event_client.post_event(Event(
561 Event.Type.PENDING_SHUTDOWN))
562 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800563
Jon Salz0697cbf2012-07-04 15:14:04 +0800564 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
565 self.invocations[test] = invoc
566 if self.visible_test is None and test.has_ui:
567 self.set_visible_test(test)
568 self.check_connection_manager()
569 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800570
Jon Salz0697cbf2012-07-04 15:14:04 +0800571 def check_connection_manager(self):
572 exclusive_tests = [
573 test.path
574 for test in self.invocations
575 if test.is_exclusive(
576 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
577 if exclusive_tests:
578 # Make sure networking is disabled.
579 if self.network_enabled:
580 logging.info('Disabling network, as requested by %s',
581 exclusive_tests)
582 self.connection_manager.DisableNetworking()
583 self.network_enabled = False
584 else:
585 # Make sure networking is enabled.
586 if not self.network_enabled:
587 logging.info('Re-enabling network')
588 self.connection_manager.EnableNetworking()
589 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800590
Jon Salz0697cbf2012-07-04 15:14:04 +0800591 def run_tests(self, subtrees, untested_only=False):
592 '''
593 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800594
Jon Salz0697cbf2012-07-04 15:14:04 +0800595 The tests are run in order unless one fails (then stops).
596 Backgroundable tests are run simultaneously; when a foreground test is
597 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800598
Jon Salz0697cbf2012-07-04 15:14:04 +0800599 @param subtrees: Node or nodes containing tests to run (may either be
600 a single test or a list). Duplicates will be ignored.
601 '''
602 if type(subtrees) != list:
603 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800604
Jon Salz0697cbf2012-07-04 15:14:04 +0800605 # Nodes we've seen so far, to avoid duplicates.
606 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800607
Jon Salz0697cbf2012-07-04 15:14:04 +0800608 self.tests_to_run = deque()
609 for subtree in subtrees:
610 for test in subtree.walk():
611 if test in seen:
612 continue
613 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800614
Jon Salz0697cbf2012-07-04 15:14:04 +0800615 if not test.is_leaf():
616 continue
617 if (untested_only and
618 test.get_state().status != TestState.UNTESTED):
619 continue
620 self.tests_to_run.append(test)
621 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800622
Jon Salz0697cbf2012-07-04 15:14:04 +0800623 def reap_completed_tests(self):
624 '''
625 Removes completed tests from the set of active tests.
626
627 Also updates the visible test if it was reaped.
628 '''
629 for t, v in dict(self.invocations).iteritems():
630 if v.is_completed():
631 del self.invocations[t]
632
633 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800634 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800635 self.set_visible_test(None)
636 # Make the first running test, if any, the visible test
637 for t in self.test_list.walk():
638 if t in self.invocations:
639 self.set_visible_test(t)
640 break
641
Jon Salz85a39882012-07-05 16:45:04 +0800642 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800643 '''
644 Kills and waits for all active tests.
645
Jon Salz85a39882012-07-05 16:45:04 +0800646 Args:
647 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800648 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800649 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800650 '''
651 self.reap_completed_tests()
652 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800653 if root and not test.has_ancestor(root):
654 continue
655
Jon Salz0697cbf2012-07-04 15:14:04 +0800656 factory.console.info('Killing active test %s...' % test.path)
657 invoc.abort_and_join()
658 factory.console.info('Killed %s' % test.path)
659 del self.invocations[test]
660 if not abort:
661 test.update_state(status=TestState.UNTESTED)
662 self.reap_completed_tests()
663
Jon Salz85a39882012-07-05 16:45:04 +0800664 def stop(self, root=None, fail=False):
665 self.kill_active_tests(fail, root)
666 # Remove any tests in the run queue under the root.
667 self.tests_to_run = deque([x for x in self.tests_to_run
668 if root and not x.has_ancestor(root)])
669 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800670
671 def abort_active_tests(self):
672 self.kill_active_tests(True)
673
674 def main(self):
675 try:
676 self.init()
677 self.event_log.Log('goofy_init',
678 success=True)
679 except:
680 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800681 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800682 self.event_log.Log('goofy_init',
683 success=False,
684 trace=traceback.format_exc())
685 except: # pylint: disable=W0702
686 pass
687 raise
688
689 self.run()
690
691 def update_system_info(self):
692 '''Updates system info.'''
693 system_info = system.SystemInfo()
694 self.state_instance.set_shared_data('system_info', system_info.__dict__)
695 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
696 system_info=system_info.__dict__))
697 logging.info('System info: %r', system_info.__dict__)
698
699 def update_factory(self):
700 self.kill_active_tests(False)
701 self.run_tests([])
702
703 try:
704 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
705 self.env.shutdown('reboot')
706 except: # pylint: disable=W0702
707 factory.console.exception('Unable to update')
708
709 def init(self, args=None, env=None):
710 '''Initializes Goofy.
711
712 Args:
713 args: A list of command-line arguments. Uses sys.argv if
714 args is None.
715 env: An Environment instance to use (or None to choose
716 FakeChrootEnvironment or DUTEnvironment as appropriate).
717 '''
718 parser = OptionParser()
719 parser.add_option('-v', '--verbose', dest='verbose',
720 action='store_true',
721 help='Enable debug logging')
722 parser.add_option('--print_test_list', dest='print_test_list',
723 metavar='FILE',
724 help='Read and print test list FILE, and exit')
725 parser.add_option('--restart', dest='restart',
726 action='store_true',
727 help='Clear all test state')
728 parser.add_option('--ui', dest='ui', type='choice',
729 choices=['none', 'gtk', 'chrome'],
730 default='gtk',
731 help='UI to use')
732 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
733 type='int', default=1,
734 help=('Factor by which to scale UI '
735 '(Chrome UI only)'))
736 parser.add_option('--test_list', dest='test_list',
737 metavar='FILE',
738 help='Use FILE as test list')
739 (self.options, self.args) = parser.parse_args(args)
740
Jon Salz46b89562012-07-05 11:49:22 +0800741 # Make sure factory directories exist.
742 factory.get_log_root()
743 factory.get_state_root()
744 factory.get_test_data_root()
745
Jon Salz0697cbf2012-07-04 15:14:04 +0800746 global _inited_logging # pylint: disable=W0603
747 if not _inited_logging:
748 factory.init_logging('goofy', verbose=self.options.verbose)
749 _inited_logging = True
750 self.event_log = EventLog('goofy')
751
752 if (not suppress_chroot_warning and
753 factory.in_chroot() and
754 self.options.ui == 'gtk' and
755 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
756 # That's not going to work! Tell the user how to run
757 # this way.
758 logging.warn(GOOFY_IN_CHROOT_WARNING)
759 time.sleep(1)
760
761 if env:
762 self.env = env
763 elif factory.in_chroot():
764 self.env = test_environment.FakeChrootEnvironment()
765 logging.warn(
766 'Using chroot environment: will not actually run autotests')
767 else:
768 self.env = test_environment.DUTEnvironment()
769 self.env.goofy = self
770
771 if self.options.restart:
772 state.clear_state()
773
774 if self.options.print_test_list:
775 print (factory.read_test_list(
776 self.options.print_test_list,
777 test_classes=dict(test_steps.__dict__)).
778 __repr__(recursive=True))
779 return
780
781 if self.options.ui_scale_factor != 1 and utils.in_qemu():
782 logging.warn(
783 'In QEMU; ignoring ui_scale_factor argument')
784 self.options.ui_scale_factor = 1
785
786 logging.info('Started')
787
788 self.start_state_server()
789 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
790 self.state_instance.set_shared_data('ui_scale_factor',
791 self.options.ui_scale_factor)
792 self.last_shutdown_time = (
793 self.state_instance.get_shared_data('shutdown_time', optional=True))
794 self.state_instance.del_shared_data('shutdown_time', optional=True)
795
796 if not self.options.test_list:
797 self.options.test_list = find_test_list()
798 if not self.options.test_list:
799 logging.error('No test list. Aborting.')
800 sys.exit(1)
801 logging.info('Using test list %s', self.options.test_list)
802
803 self.test_list = factory.read_test_list(
804 self.options.test_list,
805 self.state_instance,
806 test_classes=dict(test_steps.__dict__))
807 if not self.state_instance.has_shared_data('ui_lang'):
808 self.state_instance.set_shared_data('ui_lang',
809 self.test_list.options.ui_lang)
810 self.state_instance.set_shared_data(
811 'test_list_options',
812 self.test_list.options.__dict__)
813 self.state_instance.test_list = self.test_list
814
815 self.init_states()
816 self.start_event_server()
817 self.connection_manager = self.env.create_connection_manager(
818 self.test_list.options.wlans)
819 # Note that we create a log watcher even if
820 # sync_event_log_period_secs isn't set (no background
821 # syncing), since we may use it to flush event logs as well.
822 self.log_watcher = EventLogWatcher(
823 self.test_list.options.sync_event_log_period_secs,
824 handle_event_logs_callback=self._handle_event_logs)
825 if self.test_list.options.sync_event_log_period_secs:
826 self.log_watcher.StartWatchThread()
827
828 self.update_system_info()
829
830 os.environ['CROS_FACTORY'] = '1'
831 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
832
833 # Set CROS_UI since some behaviors in ui.py depend on the
834 # particular UI in use. TODO(jsalz): Remove this (and all
835 # places it is used) when the GTK UI is removed.
836 os.environ['CROS_UI'] = self.options.ui
837
838 if self.options.ui == 'chrome':
839 self.env.launch_chrome()
840 logging.info('Waiting for a web socket connection')
841 self.web_socket_manager.wait()
842
843 # Wait for the test widget size to be set; this is done in
844 # an asynchronous RPC so there is a small chance that the
845 # web socket might be opened first.
846 for _ in range(100): # 10 s
847 try:
848 if self.state_instance.get_shared_data('test_widget_size'):
849 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800850 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800851 pass # Retry
852 time.sleep(0.1) # 100 ms
853 else:
854 logging.warn('Never received test_widget_size from UI')
855 elif self.options.ui == 'gtk':
856 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800857
Jon Salz0697cbf2012-07-04 15:14:04 +0800858 for handler in self.on_ui_startup:
859 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800860
Jon Salz0697cbf2012-07-04 15:14:04 +0800861 self.prespawner = Prespawner()
862 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800863
Jon Salz0697cbf2012-07-04 15:14:04 +0800864 def state_change_callback(test, test_state):
865 self.event_client.post_event(
866 Event(Event.Type.STATE_CHANGE,
867 path=test.path, state=test_state))
868 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800869
Jon Salz0697cbf2012-07-04 15:14:04 +0800870 try:
871 tests_after_shutdown = self.state_instance.get_shared_data(
872 'tests_after_shutdown')
873 except KeyError:
874 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800875
Jon Salz0697cbf2012-07-04 15:14:04 +0800876 if tests_after_shutdown is not None:
877 logging.info('Resuming tests after shutdown: %s',
878 tests_after_shutdown)
879 self.state_instance.set_shared_data('tests_after_shutdown', None)
880 self.tests_to_run.extend(
881 self.test_list.lookup_path(t) for t in tests_after_shutdown)
882 self.run_queue.put(self.run_next_test)
883 else:
884 if self.test_list.options.auto_run_on_start:
885 self.run_queue.put(
886 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800887
Jon Salz0697cbf2012-07-04 15:14:04 +0800888 def run(self):
889 '''Runs Goofy.'''
890 # Process events forever.
891 while self.run_once(True):
892 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800893
Jon Salz0697cbf2012-07-04 15:14:04 +0800894 def run_once(self, block=False):
895 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800896
Jon Salz0697cbf2012-07-04 15:14:04 +0800897 Args:
898 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800899
Jon Salz0697cbf2012-07-04 15:14:04 +0800900 Returns:
901 True to keep going or False to shut down.
902 '''
903 events = utils.DrainQueue(self.run_queue)
904 if not events:
905 # Nothing on the run queue.
906 self._run_queue_idle()
907 if block:
908 # Block for at least one event...
909 events.append(self.run_queue.get())
910 # ...and grab anything else that showed up at the same
911 # time.
912 events.extend(utils.DrainQueue(self.run_queue))
Jon Salz51528e12012-07-02 18:54:45 +0800913
Jon Salz0697cbf2012-07-04 15:14:04 +0800914 for event in events:
915 if not event:
916 # Shutdown request.
917 self.run_queue.task_done()
918 return False
Jon Salz51528e12012-07-02 18:54:45 +0800919
Jon Salz0697cbf2012-07-04 15:14:04 +0800920 try:
921 event()
Jon Salz85a39882012-07-05 16:45:04 +0800922 except: # pylint: disable=W0702
923 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +0800924 self.record_exception(traceback.format_exception_only(
925 *sys.exc_info()[:2]))
926 # But keep going
927 finally:
928 self.run_queue.task_done()
929 return True
Jon Salz0405ab52012-03-16 15:26:52 +0800930
Jon Salz0697cbf2012-07-04 15:14:04 +0800931 def _run_queue_idle(self):
932 '''Invoked when the run queue has no events.'''
933 self.check_connection_manager()
Jon Salz57717ca2012-04-04 16:47:25 +0800934
Jon Salz0697cbf2012-07-04 15:14:04 +0800935 def _handle_event_logs(self, log_name, chunk):
936 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +0800937
Jon Salz0697cbf2012-07-04 15:14:04 +0800938 Attempts to upload the event logs to the shopfloor server.
939 '''
940 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
941 start_time = time.time()
942 logging.info('Syncing %s', description)
943 shopfloor_client = shopfloor.get_instance(
944 detect=True,
945 timeout=self.test_list.options.shopfloor_timeout_secs)
946 shopfloor_client.UploadEvent(log_name, chunk)
947 logging.info(
948 'Successfully synced %s in %.03f s',
949 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +0800950
Jon Salz0697cbf2012-07-04 15:14:04 +0800951 def run_tests_with_status(self, statuses_to_run, starting_at=None,
952 root=None):
953 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +0800954
Jon Salz0697cbf2012-07-04 15:14:04 +0800955 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800956
Jon Salz0697cbf2012-07-04 15:14:04 +0800957 Args:
958 starting_at: If provided, only auto-runs tests beginning with
959 this test.
960 '''
961 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800962
Jon Salz0697cbf2012-07-04 15:14:04 +0800963 if starting_at:
964 # Make sure they passed a test, not a string.
965 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +0800966
Jon Salz0697cbf2012-07-04 15:14:04 +0800967 tests_to_reset = []
968 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +0800969
Jon Salz0697cbf2012-07-04 15:14:04 +0800970 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +0800971
Jon Salz0697cbf2012-07-04 15:14:04 +0800972 for test in root.get_top_level_tests():
973 if starting_at:
974 if test == starting_at:
975 # We've found starting_at; do auto-run on all
976 # subsequent tests.
977 found_starting_at = True
978 if not found_starting_at:
979 # Don't start this guy yet
980 continue
Jon Salz0405ab52012-03-16 15:26:52 +0800981
Jon Salz0697cbf2012-07-04 15:14:04 +0800982 status = test.get_state().status
983 if status == TestState.ACTIVE or status in statuses_to_run:
984 # Reset the test (later; we will need to abort
985 # all active tests first).
986 tests_to_reset.append(test)
987 if status in statuses_to_run:
988 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800989
Jon Salz0697cbf2012-07-04 15:14:04 +0800990 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +0800991
Jon Salz0697cbf2012-07-04 15:14:04 +0800992 # Reset all statuses of the tests to run (in case any tests were active;
993 # we want them to be run again).
994 for test_to_reset in tests_to_reset:
995 for test in test_to_reset.walk():
996 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +0800997
Jon Salz0697cbf2012-07-04 15:14:04 +0800998 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +0800999
Jon Salz0697cbf2012-07-04 15:14:04 +08001000 def restart_tests(self, root=None):
1001 '''Restarts all tests.'''
1002 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001003
Jon Salz0697cbf2012-07-04 15:14:04 +08001004 self.abort_active_tests()
1005 for test in root.walk():
1006 test.update_state(status=TestState.UNTESTED)
1007 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001008
Jon Salz0697cbf2012-07-04 15:14:04 +08001009 def auto_run(self, starting_at=None, root=None):
1010 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001011
Jon Salz0697cbf2012-07-04 15:14:04 +08001012 Args:
1013 starting_at: If provide, only auto-runs tests beginning with
1014 this test.
1015 '''
1016 root = root or self.test_list
1017 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1018 starting_at=starting_at,
1019 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001020
Jon Salz0697cbf2012-07-04 15:14:04 +08001021 def re_run_failed(self, root=None):
1022 '''Re-runs failed tests.'''
1023 root = root or self.test_list
1024 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001025
Jon Salz0697cbf2012-07-04 15:14:04 +08001026 def show_review_information(self):
1027 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001028
Jon Salz0697cbf2012-07-04 15:14:04 +08001029 The information screene is rendered by main UI program (ui.py), so in
1030 goofy we only need to kill all active tests, set them as untested, and
1031 clear remaining tests.
1032 '''
1033 self.kill_active_tests(False)
1034 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001035
Jon Salz0697cbf2012-07-04 15:14:04 +08001036 def handle_switch_test(self, event):
1037 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001038
Jon Salz0697cbf2012-07-04 15:14:04 +08001039 @param event: The SWITCH_TEST event.
1040 '''
1041 test = self.test_list.lookup_path(event.path)
1042 if not test:
1043 logging.error('Unknown test %r', event.key)
1044 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001045
Jon Salz0697cbf2012-07-04 15:14:04 +08001046 invoc = self.invocations.get(test)
1047 if invoc and test.backgroundable:
1048 # Already running: just bring to the front if it
1049 # has a UI.
1050 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001051 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001052 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001053
Jon Salz0697cbf2012-07-04 15:14:04 +08001054 self.abort_active_tests()
1055 for t in test.walk():
1056 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001057
Jon Salz0697cbf2012-07-04 15:14:04 +08001058 if self.test_list.options.auto_run_on_keypress:
1059 self.auto_run(starting_at=test)
1060 else:
1061 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001062
Jon Salz0697cbf2012-07-04 15:14:04 +08001063 def wait(self):
1064 '''Waits for all pending invocations.
1065
1066 Useful for testing.
1067 '''
1068 for k, v in self.invocations.iteritems():
1069 logging.info('Waiting for %s to complete...', k)
1070 v.thread.join()
1071
1072 def check_exceptions(self):
1073 '''Raises an error if any exceptions have occurred in
1074 invocation threads.'''
1075 if self.exceptions:
1076 raise RuntimeError('Exception in invocation thread: %r' %
1077 self.exceptions)
1078
1079 def record_exception(self, msg):
1080 '''Records an exception in an invocation thread.
1081
1082 An exception with the given message will be rethrown when
1083 Goofy is destroyed.'''
1084 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001085
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001086
1087if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001088 Goofy().main()