blob: 9b7c46cb9bd5d301ae7a3d5a7e8f78574ce60fa0 [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 Salz2eaae372012-06-14 18:11:27 +0800194 Event.Type.RUN_TESTS_WITH_STATUS:
195 lambda event: self.run_tests_with_status(
196 event.status,
197 root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800198 Event.Type.REVIEW:
199 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800200 Event.Type.UPDATE_SYSTEM_INFO:
201 lambda event: self.update_system_info(),
Jon Salz37eccbd2012-05-25 16:06:52 +0800202 Event.Type.UPDATE_FACTORY:
203 lambda event: self.update_factory(),
Jon Salzf00cdc82012-05-28 18:56:17 +0800204 Event.Type.STOP:
205 lambda event: self.stop(),
Jon Salz0405ab52012-03-16 15:26:52 +0800206 }
207
Jon Salz73e0fd02012-04-04 11:46:38 +0800208 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800209 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800210
211 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800212 if self.chrome:
213 self.chrome.kill()
214 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800215 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800216 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800217 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800218 if self.web_socket_manager:
219 logging.info('Stopping web sockets')
220 self.web_socket_manager.close()
221 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800222 if self.state_server_thread:
223 logging.info('Stopping state server')
224 self.state_server.shutdown()
225 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800226 self.state_server.server_close()
227 self.state_server_thread = None
Jon Salz66f65e62012-05-24 17:40:26 +0800228 if self.state_instance:
229 self.state_instance.close()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800230 if self.event_server_thread:
231 logging.info('Stopping event server')
232 self.event_server.shutdown() # pylint: disable=E1101
233 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800234 self.event_server.server_close()
235 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800236 if self.prespawner:
237 logging.info('Stopping prespawner')
238 self.prespawner.stop()
239 self.prespawner = None
240 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800241 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800242 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800243 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800244 if self.event_log:
245 self.event_log.Close()
246 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800247 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800248 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800249
250 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800251 self.state_instance, self.state_server = (
252 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800253 logging.info('Starting state server')
254 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800255 target=self.state_server.serve_forever,
256 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800257 self.state_server_thread.start()
258
259 def start_event_server(self):
260 self.event_server = EventServer()
261 logging.info('Starting factory event server')
262 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800263 target=self.event_server.serve_forever,
264 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800265 self.event_server_thread.start()
266
267 self.event_client = EventClient(
268 callback=self.handle_event, event_loop=self.run_queue)
269
Jon Salz258a40c2012-04-19 12:34:01 +0800270 self.web_socket_manager = WebSocketManager(self.uuid)
271 self.state_server.add_handler("/event",
272 self.web_socket_manager.handle_web_socket)
273
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800274 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800275 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
276 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800277 if self.options.verbose:
278 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800279 logging.info('Starting ui %s', ui_proc_args)
280 self.ui_process = subprocess.Popen(ui_proc_args)
281 logging.info('Waiting for UI to come up...')
282 self.event_client.wait(
283 lambda event: event.type == Event.Type.UI_READY)
284 logging.info('UI has started')
285
286 def set_visible_test(self, test):
287 if self.visible_test == test:
288 return
289
290 if test:
291 test.update_state(visible=True)
292 if self.visible_test:
293 self.visible_test.update_state(visible=False)
294 self.visible_test = test
295
Jon Salz74ad3262012-03-16 14:40:55 +0800296 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800297 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800298 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800299
Jon Salz74ad3262012-03-16 14:40:55 +0800300 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800301 @param state: The test state.
302 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800303 state = test.update_state(increment_shutdown_count=1)
304 logging.info('Detected shutdown (%d of %d)',
305 state.shutdown_count, test.iterations)
Jon Salz4f6c7172012-06-11 20:45:36 +0800306
307 def log_and_update_state(status, error_msg, **kw):
308 self.event_log.Log('rebooted',
309 status=status, error_msg=error_msg, **kw)
310 test.update_state(status=status, error_msg=error_msg)
311
312 if not self.last_shutdown_time:
313 log_and_update_state(status=TestState.FAILED,
314 error_msg='Unable to read shutdown_time')
315 return
316
317 now = time.time()
318 logging.info('%.03f s passed since reboot',
319 now - self.last_shutdown_time)
320
321 if self.last_shutdown_time > now:
322 test.update_state(status=TestState.FAILED,
323 error_msg='Time moved backward during reboot')
324 elif (isinstance(test, factory.RebootStep) and
325 self.test_list.options.max_reboot_time_secs and
326 (now - self.last_shutdown_time >
327 self.test_list.options.max_reboot_time_secs)):
328 # A reboot took too long; fail. (We don't check this for
329 # HaltSteps, because the machine could be halted for a
330 # very long time, and even unplugged with battery backup,
331 # thus hosing the clock.)
332 log_and_update_state(
333 status=TestState.FAILED,
334 error_msg=('More than %d s elapsed during reboot '
335 '(%.03f s, from %s to %s)' % (
336 self.test_list.options.max_reboot_time_secs,
337 now - self.last_shutdown_time,
338 utils.TimeString(self.last_shutdown_time),
339 utils.TimeString(now))),
340 duration=(now-self.last_shutdown_time))
341 elif state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800342 # Good!
Jon Salz4f6c7172012-06-11 20:45:36 +0800343 log_and_update_state(status=TestState.PASSED,
344 duration=(now - self.last_shutdown_time),
345 error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800346 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800347 # Shut down too many times
Jon Salz4f6c7172012-06-11 20:45:36 +0800348 log_and_update_state(status=TestState.FAILED,
349 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800350 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800351 logging.info('Shift keys are depressed; cancelling restarts')
352 # Abort shutdown
Jon Salz4f6c7172012-06-11 20:45:36 +0800353 log_and_update_state(
Jon Salz73e0fd02012-04-04 11:46:38 +0800354 status=TestState.FAILED,
355 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800356 else:
Jon Salz94eb56f2012-06-12 18:01:12 +0800357 def handler():
358 if self._prompt_cancel_shutdown(test, state.shutdown_count + 1):
359 log_and_update_state(
360 status=TestState.FAILED,
361 error_msg='Shutdown aborted by operator')
362 return
363
364 # Time to shutdown again
365 log_and_update_state(
366 status=TestState.ACTIVE,
367 error_msg='',
368 iteration=state.shutdown_count)
369
370 self.event_log.Log('shutdown', operation='reboot')
371 self.state_instance.set_shared_data('shutdown_time',
Jon Salz4f6c7172012-06-11 20:45:36 +0800372 time.time())
Jon Salz94eb56f2012-06-12 18:01:12 +0800373 self.env.shutdown('reboot')
374
375 self.on_ui_startup.append(handler)
376
377 def _prompt_cancel_shutdown(self, test, iteration):
378 if self.options.ui != 'chrome':
379 return False
380
381 pending_shutdown_data = {
382 'delay_secs': test.delay_secs,
383 'time': time.time() + test.delay_secs,
384 'operation': test.operation,
385 'iteration': iteration,
386 'iterations': test.iterations,
387 }
388
389 # Create a new (threaded) event client since we
390 # don't want to use the event loop for this.
391 with EventClient() as event_client:
392 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
393 **pending_shutdown_data))
394 aborted = event_client.wait(
395 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
396 timeout=test.delay_secs) is not None
397 if aborted:
398 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
399 return aborted
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800400
401 def init_states(self):
402 '''
403 Initializes all states on startup.
404 '''
405 for test in self.test_list.get_all_tests():
406 # Make sure the state server knows about all the tests,
407 # defaulting to an untested state.
408 test.update_state(update_parent=False, visible=False)
409
Jon Salzd6361c22012-06-11 22:23:57 +0800410 var_log_messages = None
411
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800412 # Any 'active' tests should be marked as failed now.
413 for test in self.test_list.walk():
414 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800415 if state.status != TestState.ACTIVE:
416 continue
417 if isinstance(test, factory.ShutdownStep):
418 # Shutdown while the test was active - that's good.
419 self.handle_shutdown_complete(test, state)
420 else:
Jon Salzd6361c22012-06-11 22:23:57 +0800421 # Unexpected shutdown. Grab /var/log/messages for context.
422 if var_log_messages is None:
423 try:
424 var_log_messages = (
425 utils.var_log_messages_before_reboot())
426 # Write it to the log, to make it easier to
427 # correlate with /var/log/messages.
428 logging.info(
429 'Unexpected shutdown. '
430 'Tail of /var/log/messages before last reboot:\n'
431 '%s', ('\n'.join(
432 ' ' + x for x in var_log_messages)))
433 except:
434 logging.exception('Unable to grok /var/log/messages')
435 var_log_messages = []
436
437 error_msg = 'Unexpected shutdown while test was running'
438 self.event_log.Log('end_test',
439 path=test.path,
440 status=TestState.FAILED,
441 invocation=test.get_state().invocation,
442 error_msg=error_msg,
443 var_log_messages='\n'.join(var_log_messages))
444 test.update_state(
445 status=TestState.FAILED,
446 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800447
448 def show_next_active_test(self):
449 '''
450 Rotates to the next visible active test.
451 '''
452 self.reap_completed_tests()
453 active_tests = [
454 t for t in self.test_list.walk()
455 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
456 if not active_tests:
457 return
458
459 try:
460 next_test = active_tests[
461 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
462 except ValueError: # visible_test not present in active_tests
463 next_test = active_tests[0]
464
465 self.set_visible_test(next_test)
466
467 def handle_event(self, event):
468 '''
469 Handles an event from the event server.
470 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800471 handler = self.event_handlers.get(event.type)
472 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800473 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800474 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800475 # We don't register handlers for all event types - just ignore
476 # this event.
477 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800478
479 def run_next_test(self):
480 '''
481 Runs the next eligible test (or tests) in self.tests_to_run.
482 '''
483 self.reap_completed_tests()
484 while self.tests_to_run:
485 logging.debug('Tests to run: %s',
486 [x.path for x in self.tests_to_run])
487
488 test = self.tests_to_run[0]
489
490 if test in self.invocations:
491 logging.info('Next test %s is already running', test.path)
492 self.tests_to_run.popleft()
493 return
494
495 if self.invocations and not (test.backgroundable and all(
496 [x.backgroundable for x in self.invocations])):
497 logging.debug('Waiting for non-backgroundable tests to '
498 'complete before running %s', test.path)
499 return
500
501 self.tests_to_run.popleft()
502
Jon Salz74ad3262012-03-16 14:40:55 +0800503 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800504 if os.path.exists(NO_REBOOT_FILE):
505 test.update_state(
506 status=TestState.FAILED, increment_count=1,
507 error_msg=('Skipped shutdown since %s is present' %
508 NO_REBOOT_FILE))
509 continue
510
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800511 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800512 error_msg='', shutdown_count=0)
Jon Salz94eb56f2012-06-12 18:01:12 +0800513 if self._prompt_cancel_shutdown(test, 1):
514 self.event_log.Log('reboot_cancelled')
515 test.update_state(
516 status=TestState.FAILED, increment_count=1,
517 error_msg='Shutdown aborted by operator',
518 shutdown_count=0)
519 return
520
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800521 # Save pending test list in the state server
522 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800523 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800524 [t.path for t in self.tests_to_run])
Jon Salz4f6c7172012-06-11 20:45:36 +0800525 # Save shutdown time
526 self.state_instance.set_shared_data('shutdown_time',
527 time.time())
Jon Salz74ad3262012-03-16 14:40:55 +0800528
Jon Salz73e0fd02012-04-04 11:46:38 +0800529 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800530 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800531 shutdown_result = self.env.shutdown(test.operation)
532 if shutdown_result:
533 # That's all, folks!
534 self.run_queue.put(None)
535 return
536 else:
537 # Just pass (e.g., in the chroot).
538 test.update_state(status=TestState.PASSED)
539 self.state_instance.set_shared_data(
540 'tests_after_shutdown', None)
Jon Salz94eb56f2012-06-12 18:01:12 +0800541 # Send event with no fields to indicate that there is no
542 # longer a pending shutdown.
543 self.event_client.post_event(Event(
544 Event.Type.PENDING_SHUTDOWN))
Jon Salz73e0fd02012-04-04 11:46:38 +0800545 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800546
547 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
548 self.invocations[test] = invoc
549 if self.visible_test is None and test.has_ui:
550 self.set_visible_test(test)
551 invoc.start()
552
Jon Salz0405ab52012-03-16 15:26:52 +0800553 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800554 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800555 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800556
557 The tests are run in order unless one fails (then stops).
558 Backgroundable tests are run simultaneously; when a foreground test is
559 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800560
561 @param subtrees: Node or nodes containing tests to run (may either be
562 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800563 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800564 if type(subtrees) != list:
565 subtrees = [subtrees]
566
567 # Nodes we've seen so far, to avoid duplicates.
568 seen = set()
569
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800570 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800571 for subtree in subtrees:
572 for test in subtree.walk():
573 if test in seen:
574 continue
575 seen.add(test)
576
577 if not test.is_leaf():
578 continue
579 if (untested_only and
580 test.get_state().status != TestState.UNTESTED):
581 continue
582 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800583 self.run_next_test()
584
585 def reap_completed_tests(self):
586 '''
587 Removes completed tests from the set of active tests.
588
589 Also updates the visible test if it was reaped.
590 '''
591 for t, v in dict(self.invocations).iteritems():
592 if v.is_completed():
593 del self.invocations[t]
594
595 if (self.visible_test is None or
596 self.visible_test not in self.invocations):
597 self.set_visible_test(None)
598 # Make the first running test, if any, the visible test
599 for t in self.test_list.walk():
600 if t in self.invocations:
601 self.set_visible_test(t)
602 break
603
Hung-Te Lin96632362012-03-20 21:14:18 +0800604 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800605 '''
606 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800607
608 @param abort: True to change state of killed tests to FAILED, False for
609 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800610 '''
611 self.reap_completed_tests()
612 for test, invoc in self.invocations.items():
613 factory.console.info('Killing active test %s...' % test.path)
614 invoc.abort_and_join()
615 factory.console.info('Killed %s' % test.path)
616 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800617 if not abort:
618 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800619 self.reap_completed_tests()
620
Jon Salzf00cdc82012-05-28 18:56:17 +0800621 def stop(self):
622 self.kill_active_tests(False)
623 self.run_tests([])
624
Hung-Te Lin96632362012-03-20 21:14:18 +0800625 def abort_active_tests(self):
626 self.kill_active_tests(True)
627
Jon Salz73e0fd02012-04-04 11:46:38 +0800628 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800629 try:
630 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800631 self.event_log.Log('goofy_init',
632 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800633 except:
634 if self.event_log:
635 try:
Jon Salzb9038572012-05-24 10:34:51 +0800636 self.event_log.Log('goofy_init',
637 success=False,
638 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800639 except:
640 pass
641 raise
642
Jon Salz73e0fd02012-04-04 11:46:38 +0800643 self.run()
644
Jon Salz5f2a0672012-05-22 17:14:06 +0800645 def update_system_info(self):
646 '''Updates system info.'''
647 system_info = test_environment.SystemInfo(self.env, self.state_instance)
648 self.state_instance.set_shared_data('system_info', system_info.__dict__)
649 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
650 system_info=system_info.__dict__))
651 logging.info('System info: %r', system_info.__dict__)
652
Jon Salz37eccbd2012-05-25 16:06:52 +0800653 def update_factory(self):
654 self.kill_active_tests(False)
655 self.run_tests([])
656
657 try:
658 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
659 self.env.shutdown('reboot')
660 except:
661 factory.console.exception('Unable to update')
662
Jon Salz73e0fd02012-04-04 11:46:38 +0800663 def init(self, args=None, env=None):
664 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800665
666 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800667 args: A list of command-line arguments. Uses sys.argv if
668 args is None.
669 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800670 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800671 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800672 parser = OptionParser()
673 parser.add_option('-v', '--verbose', dest='verbose',
674 action='store_true',
675 help='Enable debug logging')
676 parser.add_option('--print_test_list', dest='print_test_list',
677 metavar='FILE',
678 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800679 parser.add_option('--restart', dest='restart',
680 action='store_true',
681 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800682 parser.add_option('--ui', dest='ui', type='choice',
683 choices=['none', 'gtk', 'chrome'],
684 default='gtk',
685 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800686 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
687 type='int', default=1,
688 help=('Factor by which to scale UI '
689 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800690 parser.add_option('--test_list', dest='test_list',
691 metavar='FILE',
692 help='Use FILE as test list')
693 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800694
Jon Salz73e0fd02012-04-04 11:46:38 +0800695 global _inited_logging
696 if not _inited_logging:
697 factory.init_logging('goofy', verbose=self.options.verbose)
698 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800699 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800700
Jon Salz73e0fd02012-04-04 11:46:38 +0800701 if (not suppress_chroot_warning and
702 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800703 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800704 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
705 # That's not going to work! Tell the user how to run
706 # this way.
707 logging.warn(GOOFY_IN_CHROOT_WARNING)
708 time.sleep(1)
709
Jon Salz73e0fd02012-04-04 11:46:38 +0800710 if env:
711 self.env = env
712 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800713 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800714 logging.warn(
715 'Using chroot environment: will not actually run autotests')
716 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800717 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800718 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800719
Jon Salz758e6cc2012-04-03 15:47:07 +0800720 if self.options.restart:
721 state.clear_state()
722
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800723 if self.options.print_test_list:
724 print (factory.read_test_list(self.options.print_test_list).
725 __repr__(recursive=True))
726 return
727
Jon Salz9b312912012-06-04 11:27:00 +0800728 if self.options.ui_scale_factor != 1 and factory.in_qemu():
729 logging.warn(
730 'In QEMU; ignoring ui_scale_factor argument')
731 self.options.ui_scale_factor = 1
732
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800733 logging.info('Started')
734
735 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800736 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800737 self.state_instance.set_shared_data('ui_scale_factor',
738 self.options.ui_scale_factor)
Jon Salz4f6c7172012-06-11 20:45:36 +0800739 self.last_shutdown_time = (
740 self.state_instance.get_shared_data('shutdown_time', optional=True))
741 self.state_instance.del_shared_data('shutdown_time', optional=True)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800742
Jon Salz73e0fd02012-04-04 11:46:38 +0800743 self.options.test_list = (self.options.test_list or find_test_list())
744 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800745 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800746 if not self.state_instance.has_shared_data('ui_lang'):
747 self.state_instance.set_shared_data('ui_lang',
748 self.test_list.options.ui_lang)
Jon Salzdbf398f2012-06-14 17:30:01 +0800749 self.state_instance.set_shared_data(
750 'test_list_options',
751 self.test_list.options.__dict__)
Jon Salz258a40c2012-04-19 12:34:01 +0800752 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800753
754 self.init_states()
755 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800756
Jon Salz5f2a0672012-05-22 17:14:06 +0800757 self.update_system_info()
758
Jon Salz5da61e62012-05-31 13:06:22 +0800759 os.environ['CROS_FACTORY'] = '1'
Jon Salzeebd12e2012-06-08 15:34:56 +0800760 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
Jon Salz5da61e62012-05-31 13:06:22 +0800761
Jon Salzb1b39092012-05-03 02:05:09 +0800762 # Set CROS_UI since some behaviors in ui.py depend on the
763 # particular UI in use. TODO(jsalz): Remove this (and all
764 # places it is used) when the GTK UI is removed.
765 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800766
Jon Salzb1b39092012-05-03 02:05:09 +0800767 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800768 self.env.launch_chrome()
769 logging.info('Waiting for a web socket connection')
770 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800771
772 # Wait for the test widget size to be set; this is done in
773 # an asynchronous RPC so there is a small chance that the
774 # web socket might be opened first.
775 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800776 try:
777 if self.state_instance.get_shared_data('test_widget_size'):
778 break
779 except KeyError:
780 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800781 time.sleep(0.1) # 100 ms
782 else:
783 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800784 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800785 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800786
Jon Salz94eb56f2012-06-12 18:01:12 +0800787 for handler in self.on_ui_startup:
788 handler()
789
Jon Salz8375c2e2012-04-04 15:22:24 +0800790 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800791 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800792
793 def state_change_callback(test, state):
794 self.event_client.post_event(
795 Event(Event.Type.STATE_CHANGE,
796 path=test.path, state=state))
797 self.test_list.state_change_callback = state_change_callback
798
799 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800800 tests_after_shutdown = self.state_instance.get_shared_data(
801 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800802 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800803 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800804
Jon Salz758e6cc2012-04-03 15:47:07 +0800805 if tests_after_shutdown is not None:
806 logging.info('Resuming tests after shutdown: %s',
807 tests_after_shutdown)
808 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800809 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800810 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800811 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800812 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800813 if self.test_list.options.auto_run_on_start:
814 self.run_queue.put(
815 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800816
Jon Salz73e0fd02012-04-04 11:46:38 +0800817 def run(self):
818 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800819 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800820 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800821 pass
822
Jon Salz57717ca2012-04-04 16:47:25 +0800823 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800824 '''Runs all items pending in the event loop.
825
Jon Salz57717ca2012-04-04 16:47:25 +0800826 Args:
827 block: If true, block until at least one event is processed.
828
Jon Salz73e0fd02012-04-04 11:46:38 +0800829 Returns:
830 True to keep going or False to shut down.
831 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800832 events = []
833 if block:
834 # Get at least one event
835 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800836 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800837 try:
838 events.append(self.run_queue.get_nowait())
839 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800840 break
841
Jon Salz73e0fd02012-04-04 11:46:38 +0800842 for event in events:
843 if not event:
844 # Shutdown request.
845 self.run_queue.task_done()
846 return False
847
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800848 try:
849 event()
850 except Exception as e: # pylint: disable=W0703
851 logging.error('Error in event loop: %s', e)
852 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800853 self.record_exception(traceback.format_exception_only(
854 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800855 # But keep going
856 finally:
857 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800858 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800859
Jon Salz258a40c2012-04-19 12:34:01 +0800860 def run_tests_with_status(self, statuses_to_run, starting_at=None,
861 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800862 '''Runs all top-level tests with a particular status.
863
864 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800865
866 Args:
867 starting_at: If provided, only auto-runs tests beginning with
868 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800869 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800870 root = root or self.test_list
871
Jon Salz57717ca2012-04-04 16:47:25 +0800872 if starting_at:
873 # Make sure they passed a test, not a string.
874 assert isinstance(starting_at, factory.FactoryTest)
875
Jon Salz0405ab52012-03-16 15:26:52 +0800876 tests_to_reset = []
877 tests_to_run = []
878
Jon Salz57717ca2012-04-04 16:47:25 +0800879 found_starting_at = False
880
Jon Salz258a40c2012-04-19 12:34:01 +0800881 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800882 if starting_at:
883 if test == starting_at:
884 # We've found starting_at; do auto-run on all
885 # subsequent tests.
886 found_starting_at = True
887 if not found_starting_at:
888 # Don't start this guy yet
889 continue
890
Jon Salz0405ab52012-03-16 15:26:52 +0800891 status = test.get_state().status
892 if status == TestState.ACTIVE or status in statuses_to_run:
893 # Reset the test (later; we will need to abort
894 # all active tests first).
895 tests_to_reset.append(test)
896 if status in statuses_to_run:
897 tests_to_run.append(test)
898
899 self.abort_active_tests()
900
901 # Reset all statuses of the tests to run (in case any tests were active;
902 # we want them to be run again).
903 for test_to_reset in tests_to_reset:
904 for test in test_to_reset.walk():
905 test.update_state(status=TestState.UNTESTED)
906
907 self.run_tests(tests_to_run, untested_only=True)
908
Jon Salz258a40c2012-04-19 12:34:01 +0800909 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800910 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800911 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800912
Jon Salz258a40c2012-04-19 12:34:01 +0800913 self.abort_active_tests()
914 for test in root.walk():
915 test.update_state(status=TestState.UNTESTED)
916 self.run_tests(root)
917
918 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800919 '''"Auto-runs" tests that have not been run yet.
920
921 Args:
922 starting_at: If provide, only auto-runs tests beginning with
923 this test.
924 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800925 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800926 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800927 starting_at=starting_at,
928 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800929
Jon Salz258a40c2012-04-19 12:34:01 +0800930 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800931 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800932 root = root or self.test_list
933 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800934
Jon Salz968e90b2012-03-18 16:12:43 +0800935 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800936 '''Event handler for showing review information screen.
937
938 The information screene is rendered by main UI program (ui.py), so in
939 goofy we only need to kill all active tests, set them as untested, and
940 clear remaining tests.
941 '''
942 self.kill_active_tests(False)
943 self.run_tests([])
944
Jon Salz0405ab52012-03-16 15:26:52 +0800945 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800946 '''Switches to a particular test.
947
948 @param event: The SWITCH_TEST event.
949 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800950 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800951 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800952 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800953 return
954
955 invoc = self.invocations.get(test)
956 if invoc and test.backgroundable:
957 # Already running: just bring to the front if it
958 # has a UI.
959 logging.info('Setting visible test to %s', test.path)
960 self.event_client.post_event(
961 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
962 return
963
964 self.abort_active_tests()
965 for t in test.walk():
966 t.update_state(status=TestState.UNTESTED)
967
968 if self.test_list.options.auto_run_on_keypress:
969 self.auto_run(starting_at=test)
970 else:
971 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800972
Jon Salz73e0fd02012-04-04 11:46:38 +0800973 def wait(self):
974 '''Waits for all pending invocations.
975
976 Useful for testing.
977 '''
978 for k, v in self.invocations.iteritems():
979 logging.info('Waiting for %s to complete...', k)
980 v.thread.join()
981
982 def check_exceptions(self):
983 '''Raises an error if any exceptions have occurred in
984 invocation threads.'''
985 if self.exceptions:
986 raise RuntimeError('Exception in invocation thread: %r' %
987 self.exceptions)
988
989 def record_exception(self, msg):
990 '''Records an exception in an invocation thread.
991
992 An exception with the given message will be rethrown when
993 Goofy is destroyed.'''
994 self.exceptions.append(msg)
995
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800996
997if __name__ == '__main__':
998 Goofy().main()