blob: 60761c78384ce5e76aa84b08658af543c08a282e [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 Salz73e0fd02012-04-04 11:46:38 +080013import array
14import fcntl
15import glob
Jon Salz0405ab52012-03-16 15:26:52 +080016import logging
17import os
Jon Salz258a40c2012-04-19 12:34:01 +080018import cPickle as pickle
Jon Salz0405ab52012-03-16 15:26:52 +080019import 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
Jon Salz258a40c2012-04-19 12:34:01 +080029import unittest
30import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080031from collections import deque
32from optparse import OptionParser
Jon Salz258a40c2012-04-19 12:34:01 +080033from StringIO import StringIO
Hung-Te Linf2f78f72012-02-08 19:27:11 +080034
35import factory_common
Jon Salz8375c2e2012-04-04 15:22:24 +080036from autotest_lib.client.bin.prespawner import Prespawner
Hung-Te Linf2f78f72012-02-08 19:27:11 +080037from autotest_lib.client.cros import factory
38from autotest_lib.client.cros.factory import state
39from autotest_lib.client.cros.factory import TestState
Jon Salz37eccbd2012-05-25 16:06:52 +080040from autotest_lib.client.cros.factory import updater
Jon Salz258a40c2012-04-19 12:34:01 +080041from autotest_lib.client.cros.factory import utils
Hung-Te Linf2f78f72012-02-08 19:27:11 +080042from autotest_lib.client.cros.factory.event import Event
43from autotest_lib.client.cros.factory.event import EventClient
44from autotest_lib.client.cros.factory.event import EventServer
Jon Salzeb8d25f2012-05-22 15:17:32 +080045from autotest_lib.client.cros.factory.event_log import EventLog
Jon Salz258a40c2012-04-19 12:34:01 +080046from autotest_lib.client.cros.factory.invocation import TestInvocation
Jon Salz5f2a0672012-05-22 17:14:06 +080047from autotest_lib.client.cros.factory import test_environment
Jon Salz258a40c2012-04-19 12:34:01 +080048from autotest_lib.client.cros.factory.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080049
50
Hung-Te Linf2f78f72012-02-08 19:27:11 +080051DEFAULT_TEST_LIST_PATH = os.path.join(
Jon Salz258a40c2012-04-19 12:34:01 +080052 factory.CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080053HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
54
Jon Salz8796e362012-05-24 11:39:09 +080055# File that suppresses reboot if present (e.g., for development).
56NO_REBOOT_FILE = '/var/log/factory.noreboot'
57
Jon Salz758e6cc2012-04-03 15:47:07 +080058GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
59You are running Goofy inside the chroot. Autotests are not supported.
60
61To use Goofy in the chroot, first install an Xvnc server:
62
63 sudo apt-get install tightvncserver
64
65...and then start a VNC X server outside the chroot:
66
67 vncserver :10 &
68 vncviewer :10
69
70...and run Goofy as follows:
71
72 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
73''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080074suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080075
76def get_hwid_cfg():
77 '''
78 Returns the HWID config tag, or an empty string if none can be found.
79 '''
80 if 'CROS_HWID' in os.environ:
81 return os.environ['CROS_HWID']
82 if os.path.exists(HWID_CFG_PATH):
83 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
84 return hwid_cfg_handle.read().strip()
85 return ''
86
87
88def find_test_list():
89 '''
90 Returns the path to the active test list, based on the HWID config tag.
91 '''
92 hwid_cfg = get_hwid_cfg()
93
94 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
95 if hwid_cfg:
96 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
97 if os.path.exists(test_list):
98 logging.info('Using special test list: %s', test_list)
99 return test_list
100 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
101
102 test_list = DEFAULT_TEST_LIST_PATH
103 if os.path.exists(test_list):
104 return test_list
105
106 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
107 if os.path.exists(test_list):
108 logging.info('Using default test list: ' + test_list)
109 return test_list
110 logging.info('ERROR: Cannot find any test list.')
111
Jon Salz73e0fd02012-04-04 11:46:38 +0800112
Jon Salz73e0fd02012-04-04 11:46:38 +0800113_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800114
115class Goofy(object):
116 '''
117 The main factory flow.
118
119 Note that all methods in this class must be invoked from the main
120 (event) thread. Other threads, such as callbacks and TestInvocation
121 methods, should instead post events on the run queue.
122
123 TODO: Unit tests. (chrome-os-partner:7409)
124
125 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800126 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800127 state_instance: An instance of FactoryState.
128 state_server: The FactoryState XML/RPC server.
129 state_server_thread: A thread running state_server.
130 event_server: The EventServer socket server.
131 event_server_thread: A thread running event_server.
132 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800133 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800134 run_queue: A queue of callbacks to invoke from the main thread.
135 invocations: A map from FactoryTest objects to the corresponding
136 TestInvocations objects representing active tests.
137 tests_to_run: A deque of tests that should be run when the current
138 test(s) complete.
139 options: Command-line options.
140 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800141 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800142 event_handlers: Map of Event.Type to the method used to handle that
143 event. If the method has an 'event' argument, the event is passed
144 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800145 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800146 '''
147 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800148 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800149 self.state_instance = None
150 self.state_server = None
151 self.state_server_thread = None
152 self.event_server = None
153 self.event_server_thread = None
154 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800155 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800156 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800157 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800158 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800159 self.invocations = {}
160 self.tests_to_run = deque()
161 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800162 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800163
164 self.options = None
165 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800166 self.test_list = None
Jon Salz94eb56f2012-06-12 18:01:12 +0800167 self.on_ui_startup = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800168
Jon Salz258a40c2012-04-19 12:34:01 +0800169 def test_or_root(event):
170 '''Returns the top-level parent for a test (the root node of the
171 tests that need to be run together if the given test path is to
172 be run).'''
173 try:
174 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800175 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800176 path = None
177
178 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800179 return (self.test_list.lookup_path(path).
180 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800181 else:
182 return self.test_list
183
Jon Salz0405ab52012-03-16 15:26:52 +0800184 self.event_handlers = {
185 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800186 Event.Type.SHOW_NEXT_ACTIVE_TEST:
187 lambda event: self.show_next_active_test(),
188 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800189 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800190 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800191 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800192 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800193 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800194 Event.Type.REVIEW:
195 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800196 Event.Type.UPDATE_SYSTEM_INFO:
197 lambda event: self.update_system_info(),
Jon Salz37eccbd2012-05-25 16:06:52 +0800198 Event.Type.UPDATE_FACTORY:
199 lambda event: self.update_factory(),
Jon Salzf00cdc82012-05-28 18:56:17 +0800200 Event.Type.STOP:
201 lambda event: self.stop(),
Jon Salz0405ab52012-03-16 15:26:52 +0800202 }
203
Jon Salz73e0fd02012-04-04 11:46:38 +0800204 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800205 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800206
207 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800208 if self.chrome:
209 self.chrome.kill()
210 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800211 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800212 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800213 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800214 if self.web_socket_manager:
215 logging.info('Stopping web sockets')
216 self.web_socket_manager.close()
217 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800218 if self.state_server_thread:
219 logging.info('Stopping state server')
220 self.state_server.shutdown()
221 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800222 self.state_server.server_close()
223 self.state_server_thread = None
Jon Salz66f65e62012-05-24 17:40:26 +0800224 if self.state_instance:
225 self.state_instance.close()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800226 if self.event_server_thread:
227 logging.info('Stopping event server')
228 self.event_server.shutdown() # pylint: disable=E1101
229 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800230 self.event_server.server_close()
231 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800232 if self.prespawner:
233 logging.info('Stopping prespawner')
234 self.prespawner.stop()
235 self.prespawner = None
236 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800237 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800238 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800239 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800240 if self.event_log:
241 self.event_log.Close()
242 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800243 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800244 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800245
246 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800247 self.state_instance, self.state_server = (
248 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800249 logging.info('Starting state server')
250 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800251 target=self.state_server.serve_forever,
252 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800253 self.state_server_thread.start()
254
255 def start_event_server(self):
256 self.event_server = EventServer()
257 logging.info('Starting factory event server')
258 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800259 target=self.event_server.serve_forever,
260 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800261 self.event_server_thread.start()
262
263 self.event_client = EventClient(
264 callback=self.handle_event, event_loop=self.run_queue)
265
Jon Salz258a40c2012-04-19 12:34:01 +0800266 self.web_socket_manager = WebSocketManager(self.uuid)
267 self.state_server.add_handler("/event",
268 self.web_socket_manager.handle_web_socket)
269
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800270 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800271 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
272 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800273 if self.options.verbose:
274 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800275 logging.info('Starting ui %s', ui_proc_args)
276 self.ui_process = subprocess.Popen(ui_proc_args)
277 logging.info('Waiting for UI to come up...')
278 self.event_client.wait(
279 lambda event: event.type == Event.Type.UI_READY)
280 logging.info('UI has started')
281
282 def set_visible_test(self, test):
283 if self.visible_test == test:
284 return
285
286 if test:
287 test.update_state(visible=True)
288 if self.visible_test:
289 self.visible_test.update_state(visible=False)
290 self.visible_test = test
291
Jon Salz74ad3262012-03-16 14:40:55 +0800292 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800293 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800294 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800295
Jon Salz74ad3262012-03-16 14:40:55 +0800296 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800297 @param state: The test state.
298 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800299 state = test.update_state(increment_shutdown_count=1)
300 logging.info('Detected shutdown (%d of %d)',
301 state.shutdown_count, test.iterations)
Jon Salz4f6c7172012-06-11 20:45:36 +0800302
303 def log_and_update_state(status, error_msg, **kw):
304 self.event_log.Log('rebooted',
305 status=status, error_msg=error_msg, **kw)
306 test.update_state(status=status, error_msg=error_msg)
307
308 if not self.last_shutdown_time:
309 log_and_update_state(status=TestState.FAILED,
310 error_msg='Unable to read shutdown_time')
311 return
312
313 now = time.time()
314 logging.info('%.03f s passed since reboot',
315 now - self.last_shutdown_time)
316
317 if self.last_shutdown_time > now:
318 test.update_state(status=TestState.FAILED,
319 error_msg='Time moved backward during reboot')
320 elif (isinstance(test, factory.RebootStep) and
321 self.test_list.options.max_reboot_time_secs and
322 (now - self.last_shutdown_time >
323 self.test_list.options.max_reboot_time_secs)):
324 # A reboot took too long; fail. (We don't check this for
325 # HaltSteps, because the machine could be halted for a
326 # very long time, and even unplugged with battery backup,
327 # thus hosing the clock.)
328 log_and_update_state(
329 status=TestState.FAILED,
330 error_msg=('More than %d s elapsed during reboot '
331 '(%.03f s, from %s to %s)' % (
332 self.test_list.options.max_reboot_time_secs,
333 now - self.last_shutdown_time,
334 utils.TimeString(self.last_shutdown_time),
335 utils.TimeString(now))),
336 duration=(now-self.last_shutdown_time))
337 elif state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800338 # Good!
Jon Salz4f6c7172012-06-11 20:45:36 +0800339 log_and_update_state(status=TestState.PASSED,
340 duration=(now - self.last_shutdown_time),
341 error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800342 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800343 # Shut down too many times
Jon Salz4f6c7172012-06-11 20:45:36 +0800344 log_and_update_state(status=TestState.FAILED,
345 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800346 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800347 logging.info('Shift keys are depressed; cancelling restarts')
348 # Abort shutdown
Jon Salz4f6c7172012-06-11 20:45:36 +0800349 log_and_update_state(
Jon Salz73e0fd02012-04-04 11:46:38 +0800350 status=TestState.FAILED,
351 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800352 else:
Jon Salz94eb56f2012-06-12 18:01:12 +0800353 def handler():
354 if self._prompt_cancel_shutdown(test, state.shutdown_count + 1):
355 log_and_update_state(
356 status=TestState.FAILED,
357 error_msg='Shutdown aborted by operator')
358 return
359
360 # Time to shutdown again
361 log_and_update_state(
362 status=TestState.ACTIVE,
363 error_msg='',
364 iteration=state.shutdown_count)
365
366 self.event_log.Log('shutdown', operation='reboot')
367 self.state_instance.set_shared_data('shutdown_time',
Jon Salz4f6c7172012-06-11 20:45:36 +0800368 time.time())
Jon Salz94eb56f2012-06-12 18:01:12 +0800369 self.env.shutdown('reboot')
370
371 self.on_ui_startup.append(handler)
372
373 def _prompt_cancel_shutdown(self, test, iteration):
374 if self.options.ui != 'chrome':
375 return False
376
377 pending_shutdown_data = {
378 'delay_secs': test.delay_secs,
379 'time': time.time() + test.delay_secs,
380 'operation': test.operation,
381 'iteration': iteration,
382 'iterations': test.iterations,
383 }
384
385 # Create a new (threaded) event client since we
386 # don't want to use the event loop for this.
387 with EventClient() as event_client:
388 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
389 **pending_shutdown_data))
390 aborted = event_client.wait(
391 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
392 timeout=test.delay_secs) is not None
393 if aborted:
394 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
395 return aborted
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396
397 def init_states(self):
398 '''
399 Initializes all states on startup.
400 '''
401 for test in self.test_list.get_all_tests():
402 # Make sure the state server knows about all the tests,
403 # defaulting to an untested state.
404 test.update_state(update_parent=False, visible=False)
405
Jon Salzd6361c22012-06-11 22:23:57 +0800406 var_log_messages = None
407
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800408 # Any 'active' tests should be marked as failed now.
409 for test in self.test_list.walk():
410 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800411 if state.status != TestState.ACTIVE:
412 continue
413 if isinstance(test, factory.ShutdownStep):
414 # Shutdown while the test was active - that's good.
415 self.handle_shutdown_complete(test, state)
416 else:
Jon Salzd6361c22012-06-11 22:23:57 +0800417 # Unexpected shutdown. Grab /var/log/messages for context.
418 if var_log_messages is None:
419 try:
420 var_log_messages = (
421 utils.var_log_messages_before_reboot())
422 # Write it to the log, to make it easier to
423 # correlate with /var/log/messages.
424 logging.info(
425 'Unexpected shutdown. '
426 'Tail of /var/log/messages before last reboot:\n'
427 '%s', ('\n'.join(
428 ' ' + x for x in var_log_messages)))
429 except:
430 logging.exception('Unable to grok /var/log/messages')
431 var_log_messages = []
432
433 error_msg = 'Unexpected shutdown while test was running'
434 self.event_log.Log('end_test',
435 path=test.path,
436 status=TestState.FAILED,
437 invocation=test.get_state().invocation,
438 error_msg=error_msg,
439 var_log_messages='\n'.join(var_log_messages))
440 test.update_state(
441 status=TestState.FAILED,
442 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800443
444 def show_next_active_test(self):
445 '''
446 Rotates to the next visible active test.
447 '''
448 self.reap_completed_tests()
449 active_tests = [
450 t for t in self.test_list.walk()
451 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
452 if not active_tests:
453 return
454
455 try:
456 next_test = active_tests[
457 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
458 except ValueError: # visible_test not present in active_tests
459 next_test = active_tests[0]
460
461 self.set_visible_test(next_test)
462
463 def handle_event(self, event):
464 '''
465 Handles an event from the event server.
466 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800467 handler = self.event_handlers.get(event.type)
468 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800469 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800470 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800471 # We don't register handlers for all event types - just ignore
472 # this event.
473 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800474
475 def run_next_test(self):
476 '''
477 Runs the next eligible test (or tests) in self.tests_to_run.
478 '''
479 self.reap_completed_tests()
480 while self.tests_to_run:
481 logging.debug('Tests to run: %s',
482 [x.path for x in self.tests_to_run])
483
484 test = self.tests_to_run[0]
485
486 if test in self.invocations:
487 logging.info('Next test %s is already running', test.path)
488 self.tests_to_run.popleft()
489 return
490
491 if self.invocations and not (test.backgroundable and all(
492 [x.backgroundable for x in self.invocations])):
493 logging.debug('Waiting for non-backgroundable tests to '
494 'complete before running %s', test.path)
495 return
496
497 self.tests_to_run.popleft()
498
Jon Salz74ad3262012-03-16 14:40:55 +0800499 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800500 if os.path.exists(NO_REBOOT_FILE):
501 test.update_state(
502 status=TestState.FAILED, increment_count=1,
503 error_msg=('Skipped shutdown since %s is present' %
504 NO_REBOOT_FILE))
505 continue
506
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800507 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800508 error_msg='', shutdown_count=0)
Jon Salz94eb56f2012-06-12 18:01:12 +0800509 if self._prompt_cancel_shutdown(test, 1):
510 self.event_log.Log('reboot_cancelled')
511 test.update_state(
512 status=TestState.FAILED, increment_count=1,
513 error_msg='Shutdown aborted by operator',
514 shutdown_count=0)
515 return
516
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800517 # Save pending test list in the state server
518 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800519 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800520 [t.path for t in self.tests_to_run])
Jon Salz4f6c7172012-06-11 20:45:36 +0800521 # Save shutdown time
522 self.state_instance.set_shared_data('shutdown_time',
523 time.time())
Jon Salz74ad3262012-03-16 14:40:55 +0800524
Jon Salz73e0fd02012-04-04 11:46:38 +0800525 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800526 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800527 shutdown_result = self.env.shutdown(test.operation)
528 if shutdown_result:
529 # That's all, folks!
530 self.run_queue.put(None)
531 return
532 else:
533 # Just pass (e.g., in the chroot).
534 test.update_state(status=TestState.PASSED)
535 self.state_instance.set_shared_data(
536 'tests_after_shutdown', None)
Jon Salz94eb56f2012-06-12 18:01:12 +0800537 # Send event with no fields to indicate that there is no
538 # longer a pending shutdown.
539 self.event_client.post_event(Event(
540 Event.Type.PENDING_SHUTDOWN))
Jon Salz73e0fd02012-04-04 11:46:38 +0800541 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800542
543 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
544 self.invocations[test] = invoc
545 if self.visible_test is None and test.has_ui:
546 self.set_visible_test(test)
547 invoc.start()
548
Jon Salz0405ab52012-03-16 15:26:52 +0800549 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800550 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800551 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800552
553 The tests are run in order unless one fails (then stops).
554 Backgroundable tests are run simultaneously; when a foreground test is
555 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800556
557 @param subtrees: Node or nodes containing tests to run (may either be
558 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800559 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800560 if type(subtrees) != list:
561 subtrees = [subtrees]
562
563 # Nodes we've seen so far, to avoid duplicates.
564 seen = set()
565
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800566 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800567 for subtree in subtrees:
568 for test in subtree.walk():
569 if test in seen:
570 continue
571 seen.add(test)
572
573 if not test.is_leaf():
574 continue
575 if (untested_only and
576 test.get_state().status != TestState.UNTESTED):
577 continue
578 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579 self.run_next_test()
580
581 def reap_completed_tests(self):
582 '''
583 Removes completed tests from the set of active tests.
584
585 Also updates the visible test if it was reaped.
586 '''
587 for t, v in dict(self.invocations).iteritems():
588 if v.is_completed():
589 del self.invocations[t]
590
591 if (self.visible_test is None or
592 self.visible_test not in self.invocations):
593 self.set_visible_test(None)
594 # Make the first running test, if any, the visible test
595 for t in self.test_list.walk():
596 if t in self.invocations:
597 self.set_visible_test(t)
598 break
599
Hung-Te Lin96632362012-03-20 21:14:18 +0800600 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800601 '''
602 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800603
604 @param abort: True to change state of killed tests to FAILED, False for
605 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800606 '''
607 self.reap_completed_tests()
608 for test, invoc in self.invocations.items():
609 factory.console.info('Killing active test %s...' % test.path)
610 invoc.abort_and_join()
611 factory.console.info('Killed %s' % test.path)
612 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800613 if not abort:
614 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800615 self.reap_completed_tests()
616
Jon Salzf00cdc82012-05-28 18:56:17 +0800617 def stop(self):
618 self.kill_active_tests(False)
619 self.run_tests([])
620
Hung-Te Lin96632362012-03-20 21:14:18 +0800621 def abort_active_tests(self):
622 self.kill_active_tests(True)
623
Jon Salz73e0fd02012-04-04 11:46:38 +0800624 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800625 try:
626 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800627 self.event_log.Log('goofy_init',
628 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800629 except:
630 if self.event_log:
631 try:
Jon Salzb9038572012-05-24 10:34:51 +0800632 self.event_log.Log('goofy_init',
633 success=False,
634 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800635 except:
636 pass
637 raise
638
Jon Salz73e0fd02012-04-04 11:46:38 +0800639 self.run()
640
Jon Salz5f2a0672012-05-22 17:14:06 +0800641 def update_system_info(self):
642 '''Updates system info.'''
643 system_info = test_environment.SystemInfo(self.env, self.state_instance)
644 self.state_instance.set_shared_data('system_info', system_info.__dict__)
645 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
646 system_info=system_info.__dict__))
647 logging.info('System info: %r', system_info.__dict__)
648
Jon Salz37eccbd2012-05-25 16:06:52 +0800649 def update_factory(self):
650 self.kill_active_tests(False)
651 self.run_tests([])
652
653 try:
654 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
655 self.env.shutdown('reboot')
656 except:
657 factory.console.exception('Unable to update')
658
Jon Salz73e0fd02012-04-04 11:46:38 +0800659 def init(self, args=None, env=None):
660 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800661
662 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800663 args: A list of command-line arguments. Uses sys.argv if
664 args is None.
665 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800666 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800667 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800668 parser = OptionParser()
669 parser.add_option('-v', '--verbose', dest='verbose',
670 action='store_true',
671 help='Enable debug logging')
672 parser.add_option('--print_test_list', dest='print_test_list',
673 metavar='FILE',
674 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800675 parser.add_option('--restart', dest='restart',
676 action='store_true',
677 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800678 parser.add_option('--ui', dest='ui', type='choice',
679 choices=['none', 'gtk', 'chrome'],
680 default='gtk',
681 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800682 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
683 type='int', default=1,
684 help=('Factor by which to scale UI '
685 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800686 parser.add_option('--test_list', dest='test_list',
687 metavar='FILE',
688 help='Use FILE as test list')
689 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800690
Jon Salz73e0fd02012-04-04 11:46:38 +0800691 global _inited_logging
692 if not _inited_logging:
693 factory.init_logging('goofy', verbose=self.options.verbose)
694 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800695 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800696
Jon Salz73e0fd02012-04-04 11:46:38 +0800697 if (not suppress_chroot_warning and
698 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800699 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800700 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
701 # That's not going to work! Tell the user how to run
702 # this way.
703 logging.warn(GOOFY_IN_CHROOT_WARNING)
704 time.sleep(1)
705
Jon Salz73e0fd02012-04-04 11:46:38 +0800706 if env:
707 self.env = env
708 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800709 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800710 logging.warn(
711 'Using chroot environment: will not actually run autotests')
712 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800713 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800714 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800715
Jon Salz758e6cc2012-04-03 15:47:07 +0800716 if self.options.restart:
717 state.clear_state()
718
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800719 if self.options.print_test_list:
720 print (factory.read_test_list(self.options.print_test_list).
721 __repr__(recursive=True))
722 return
723
Jon Salz9b312912012-06-04 11:27:00 +0800724 if self.options.ui_scale_factor != 1 and factory.in_qemu():
725 logging.warn(
726 'In QEMU; ignoring ui_scale_factor argument')
727 self.options.ui_scale_factor = 1
728
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800729 logging.info('Started')
730
731 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800732 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800733 self.state_instance.set_shared_data('ui_scale_factor',
734 self.options.ui_scale_factor)
Jon Salz4f6c7172012-06-11 20:45:36 +0800735 self.last_shutdown_time = (
736 self.state_instance.get_shared_data('shutdown_time', optional=True))
737 self.state_instance.del_shared_data('shutdown_time', optional=True)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800738
Jon Salz73e0fd02012-04-04 11:46:38 +0800739 self.options.test_list = (self.options.test_list or find_test_list())
740 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800741 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800742 if not self.state_instance.has_shared_data('ui_lang'):
743 self.state_instance.set_shared_data('ui_lang',
744 self.test_list.options.ui_lang)
Jon Salz258a40c2012-04-19 12:34:01 +0800745 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800746
747 self.init_states()
748 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800749
Jon Salz5f2a0672012-05-22 17:14:06 +0800750 self.update_system_info()
751
Jon Salz5da61e62012-05-31 13:06:22 +0800752 os.environ['CROS_FACTORY'] = '1'
Jon Salzeebd12e2012-06-08 15:34:56 +0800753 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
Jon Salz5da61e62012-05-31 13:06:22 +0800754
Jon Salzb1b39092012-05-03 02:05:09 +0800755 # Set CROS_UI since some behaviors in ui.py depend on the
756 # particular UI in use. TODO(jsalz): Remove this (and all
757 # places it is used) when the GTK UI is removed.
758 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800759
Jon Salzb1b39092012-05-03 02:05:09 +0800760 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800761 self.env.launch_chrome()
762 logging.info('Waiting for a web socket connection')
763 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800764
765 # Wait for the test widget size to be set; this is done in
766 # an asynchronous RPC so there is a small chance that the
767 # web socket might be opened first.
768 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800769 try:
770 if self.state_instance.get_shared_data('test_widget_size'):
771 break
772 except KeyError:
773 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800774 time.sleep(0.1) # 100 ms
775 else:
776 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800777 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800778 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800779
Jon Salz94eb56f2012-06-12 18:01:12 +0800780 for handler in self.on_ui_startup:
781 handler()
782
Jon Salz8375c2e2012-04-04 15:22:24 +0800783 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800784 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800785
786 def state_change_callback(test, state):
787 self.event_client.post_event(
788 Event(Event.Type.STATE_CHANGE,
789 path=test.path, state=state))
790 self.test_list.state_change_callback = state_change_callback
791
792 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800793 tests_after_shutdown = self.state_instance.get_shared_data(
794 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800795 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800796 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800797
Jon Salz758e6cc2012-04-03 15:47:07 +0800798 if tests_after_shutdown is not None:
799 logging.info('Resuming tests after shutdown: %s',
800 tests_after_shutdown)
801 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800802 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800803 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800804 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800805 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800806 if self.test_list.options.auto_run_on_start:
807 self.run_queue.put(
808 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800809
Jon Salz73e0fd02012-04-04 11:46:38 +0800810 def run(self):
811 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800812 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800813 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800814 pass
815
Jon Salz57717ca2012-04-04 16:47:25 +0800816 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800817 '''Runs all items pending in the event loop.
818
Jon Salz57717ca2012-04-04 16:47:25 +0800819 Args:
820 block: If true, block until at least one event is processed.
821
Jon Salz73e0fd02012-04-04 11:46:38 +0800822 Returns:
823 True to keep going or False to shut down.
824 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800825 events = []
826 if block:
827 # Get at least one event
828 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800829 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800830 try:
831 events.append(self.run_queue.get_nowait())
832 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800833 break
834
Jon Salz73e0fd02012-04-04 11:46:38 +0800835 for event in events:
836 if not event:
837 # Shutdown request.
838 self.run_queue.task_done()
839 return False
840
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800841 try:
842 event()
843 except Exception as e: # pylint: disable=W0703
844 logging.error('Error in event loop: %s', e)
845 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800846 self.record_exception(traceback.format_exception_only(
847 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800848 # But keep going
849 finally:
850 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800851 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800852
Jon Salz258a40c2012-04-19 12:34:01 +0800853 def run_tests_with_status(self, statuses_to_run, starting_at=None,
854 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800855 '''Runs all top-level tests with a particular status.
856
857 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800858
859 Args:
860 starting_at: If provided, only auto-runs tests beginning with
861 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800862 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800863 root = root or self.test_list
864
Jon Salz57717ca2012-04-04 16:47:25 +0800865 if starting_at:
866 # Make sure they passed a test, not a string.
867 assert isinstance(starting_at, factory.FactoryTest)
868
Jon Salz0405ab52012-03-16 15:26:52 +0800869 tests_to_reset = []
870 tests_to_run = []
871
Jon Salz57717ca2012-04-04 16:47:25 +0800872 found_starting_at = False
873
Jon Salz258a40c2012-04-19 12:34:01 +0800874 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800875 if starting_at:
876 if test == starting_at:
877 # We've found starting_at; do auto-run on all
878 # subsequent tests.
879 found_starting_at = True
880 if not found_starting_at:
881 # Don't start this guy yet
882 continue
883
Jon Salz0405ab52012-03-16 15:26:52 +0800884 status = test.get_state().status
885 if status == TestState.ACTIVE or status in statuses_to_run:
886 # Reset the test (later; we will need to abort
887 # all active tests first).
888 tests_to_reset.append(test)
889 if status in statuses_to_run:
890 tests_to_run.append(test)
891
892 self.abort_active_tests()
893
894 # Reset all statuses of the tests to run (in case any tests were active;
895 # we want them to be run again).
896 for test_to_reset in tests_to_reset:
897 for test in test_to_reset.walk():
898 test.update_state(status=TestState.UNTESTED)
899
900 self.run_tests(tests_to_run, untested_only=True)
901
Jon Salz258a40c2012-04-19 12:34:01 +0800902 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800903 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800904 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800905
Jon Salz258a40c2012-04-19 12:34:01 +0800906 self.abort_active_tests()
907 for test in root.walk():
908 test.update_state(status=TestState.UNTESTED)
909 self.run_tests(root)
910
911 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800912 '''"Auto-runs" tests that have not been run yet.
913
914 Args:
915 starting_at: If provide, only auto-runs tests beginning with
916 this test.
917 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800918 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800919 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800920 starting_at=starting_at,
921 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800922
Jon Salz258a40c2012-04-19 12:34:01 +0800923 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800924 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800925 root = root or self.test_list
926 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800927
Jon Salz968e90b2012-03-18 16:12:43 +0800928 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800929 '''Event handler for showing review information screen.
930
931 The information screene is rendered by main UI program (ui.py), so in
932 goofy we only need to kill all active tests, set them as untested, and
933 clear remaining tests.
934 '''
935 self.kill_active_tests(False)
936 self.run_tests([])
937
Jon Salz0405ab52012-03-16 15:26:52 +0800938 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800939 '''Switches to a particular test.
940
941 @param event: The SWITCH_TEST event.
942 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800943 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800944 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800945 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800946 return
947
948 invoc = self.invocations.get(test)
949 if invoc and test.backgroundable:
950 # Already running: just bring to the front if it
951 # has a UI.
952 logging.info('Setting visible test to %s', test.path)
953 self.event_client.post_event(
954 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
955 return
956
957 self.abort_active_tests()
958 for t in test.walk():
959 t.update_state(status=TestState.UNTESTED)
960
961 if self.test_list.options.auto_run_on_keypress:
962 self.auto_run(starting_at=test)
963 else:
964 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800965
Jon Salz73e0fd02012-04-04 11:46:38 +0800966 def wait(self):
967 '''Waits for all pending invocations.
968
969 Useful for testing.
970 '''
971 for k, v in self.invocations.iteritems():
972 logging.info('Waiting for %s to complete...', k)
973 v.thread.join()
974
975 def check_exceptions(self):
976 '''Raises an error if any exceptions have occurred in
977 invocation threads.'''
978 if self.exceptions:
979 raise RuntimeError('Exception in invocation thread: %r' %
980 self.exceptions)
981
982 def record_exception(self, msg):
983 '''Records an exception in an invocation thread.
984
985 An exception with the given message will be rethrown when
986 Goofy is destroyed.'''
987 self.exceptions.append(msg)
988
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800989
990if __name__ == '__main__':
991 Goofy().main()