blob: 0ad987707f2b7da0c4f99b9f6969b81593c411ed [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 Salz49a7d152012-06-19 15:04:09 +080047from autotest_lib.client.cros.factory import system
Jon Salz5f2a0672012-05-22 17:14:06 +080048from autotest_lib.client.cros.factory import test_environment
Jon Salz258a40c2012-04-19 12:34:01 +080049from autotest_lib.client.cros.factory.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080050
51
Hung-Te Linf2f78f72012-02-08 19:27:11 +080052DEFAULT_TEST_LIST_PATH = os.path.join(
Jon Salz258a40c2012-04-19 12:34:01 +080053 factory.CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080054HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
55
Jon Salz8796e362012-05-24 11:39:09 +080056# File that suppresses reboot if present (e.g., for development).
57NO_REBOOT_FILE = '/var/log/factory.noreboot'
58
Jon Salz758e6cc2012-04-03 15:47:07 +080059GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
60You are running Goofy inside the chroot. Autotests are not supported.
61
62To use Goofy in the chroot, first install an Xvnc server:
63
64 sudo apt-get install tightvncserver
65
66...and then start a VNC X server outside the chroot:
67
68 vncserver :10 &
69 vncviewer :10
70
71...and run Goofy as follows:
72
73 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
74''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080075suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080076
77def get_hwid_cfg():
78 '''
79 Returns the HWID config tag, or an empty string if none can be found.
80 '''
81 if 'CROS_HWID' in os.environ:
82 return os.environ['CROS_HWID']
83 if os.path.exists(HWID_CFG_PATH):
84 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
85 return hwid_cfg_handle.read().strip()
86 return ''
87
88
89def find_test_list():
90 '''
91 Returns the path to the active test list, based on the HWID config tag.
92 '''
93 hwid_cfg = get_hwid_cfg()
94
95 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
96 if hwid_cfg:
97 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
98 if os.path.exists(test_list):
99 logging.info('Using special test list: %s', test_list)
100 return test_list
101 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
102
103 test_list = DEFAULT_TEST_LIST_PATH
104 if os.path.exists(test_list):
105 return test_list
106
107 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
108 if os.path.exists(test_list):
109 logging.info('Using default test list: ' + test_list)
110 return test_list
111 logging.info('ERROR: Cannot find any test list.')
112
Jon Salz73e0fd02012-04-04 11:46:38 +0800113
Jon Salz73e0fd02012-04-04 11:46:38 +0800114_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800115
116class Goofy(object):
117 '''
118 The main factory flow.
119
120 Note that all methods in this class must be invoked from the main
121 (event) thread. Other threads, such as callbacks and TestInvocation
122 methods, should instead post events on the run queue.
123
124 TODO: Unit tests. (chrome-os-partner:7409)
125
126 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800127 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800128 state_instance: An instance of FactoryState.
129 state_server: The FactoryState XML/RPC server.
130 state_server_thread: A thread running state_server.
131 event_server: The EventServer socket server.
132 event_server_thread: A thread running event_server.
133 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800134 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800135 run_queue: A queue of callbacks to invoke from the main thread.
136 invocations: A map from FactoryTest objects to the corresponding
137 TestInvocations objects representing active tests.
138 tests_to_run: A deque of tests that should be run when the current
139 test(s) complete.
140 options: Command-line options.
141 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800142 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800143 event_handlers: Map of Event.Type to the method used to handle that
144 event. If the method has an 'event' argument, the event is passed
145 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800146 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800147 '''
148 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800149 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800150 self.state_instance = None
151 self.state_server = None
152 self.state_server_thread = None
153 self.event_server = None
154 self.event_server_thread = None
155 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800156 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800157 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800158 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800159 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800160 self.invocations = {}
161 self.tests_to_run = deque()
162 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800163 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800164
165 self.options = None
166 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800167 self.test_list = None
Jon Salz94eb56f2012-06-12 18:01:12 +0800168 self.on_ui_startup = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800169
Jon Salz258a40c2012-04-19 12:34:01 +0800170 def test_or_root(event):
171 '''Returns the top-level parent for a test (the root node of the
172 tests that need to be run together if the given test path is to
173 be run).'''
174 try:
175 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800176 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800177 path = None
178
179 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800180 return (self.test_list.lookup_path(path).
181 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800182 else:
183 return self.test_list
184
Jon Salz0405ab52012-03-16 15:26:52 +0800185 self.event_handlers = {
186 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800187 Event.Type.SHOW_NEXT_ACTIVE_TEST:
188 lambda event: self.show_next_active_test(),
189 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800190 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800191 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800192 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800193 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800194 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz2eaae372012-06-14 18:11:27 +0800195 Event.Type.RUN_TESTS_WITH_STATUS:
196 lambda event: self.run_tests_with_status(
197 event.status,
198 root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800199 Event.Type.REVIEW:
200 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800201 Event.Type.UPDATE_SYSTEM_INFO:
202 lambda event: self.update_system_info(),
Jon Salz37eccbd2012-05-25 16:06:52 +0800203 Event.Type.UPDATE_FACTORY:
204 lambda event: self.update_factory(),
Jon Salzf00cdc82012-05-28 18:56:17 +0800205 Event.Type.STOP:
206 lambda event: self.stop(),
Jon Salz0405ab52012-03-16 15:26:52 +0800207 }
208
Jon Salz73e0fd02012-04-04 11:46:38 +0800209 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800210 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800211
212 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800213 if self.chrome:
214 self.chrome.kill()
215 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800216 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800217 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800218 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800219 if self.web_socket_manager:
220 logging.info('Stopping web sockets')
221 self.web_socket_manager.close()
222 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800223 if self.state_server_thread:
224 logging.info('Stopping state server')
225 self.state_server.shutdown()
226 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800227 self.state_server.server_close()
228 self.state_server_thread = None
Jon Salz66f65e62012-05-24 17:40:26 +0800229 if self.state_instance:
230 self.state_instance.close()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800231 if self.event_server_thread:
232 logging.info('Stopping event server')
233 self.event_server.shutdown() # pylint: disable=E1101
234 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800235 self.event_server.server_close()
236 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800237 if self.prespawner:
238 logging.info('Stopping prespawner')
239 self.prespawner.stop()
240 self.prespawner = None
241 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800242 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800243 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800244 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800245 if self.event_log:
246 self.event_log.Close()
247 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800248 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800249 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800250
251 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800252 self.state_instance, self.state_server = (
253 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800254 logging.info('Starting state server')
255 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800256 target=self.state_server.serve_forever,
257 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800258 self.state_server_thread.start()
259
260 def start_event_server(self):
261 self.event_server = EventServer()
262 logging.info('Starting factory event server')
263 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800264 target=self.event_server.serve_forever,
265 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800266 self.event_server_thread.start()
267
268 self.event_client = EventClient(
269 callback=self.handle_event, event_loop=self.run_queue)
270
Jon Salz258a40c2012-04-19 12:34:01 +0800271 self.web_socket_manager = WebSocketManager(self.uuid)
272 self.state_server.add_handler("/event",
273 self.web_socket_manager.handle_web_socket)
274
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800275 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800276 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
277 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800278 if self.options.verbose:
279 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800280 logging.info('Starting ui %s', ui_proc_args)
281 self.ui_process = subprocess.Popen(ui_proc_args)
282 logging.info('Waiting for UI to come up...')
283 self.event_client.wait(
284 lambda event: event.type == Event.Type.UI_READY)
285 logging.info('UI has started')
286
287 def set_visible_test(self, test):
288 if self.visible_test == test:
289 return
290
291 if test:
292 test.update_state(visible=True)
293 if self.visible_test:
294 self.visible_test.update_state(visible=False)
295 self.visible_test = test
296
Jon Salz74ad3262012-03-16 14:40:55 +0800297 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800298 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800299 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800300
Jon Salz74ad3262012-03-16 14:40:55 +0800301 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800302 @param state: The test state.
303 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800304 state = test.update_state(increment_shutdown_count=1)
305 logging.info('Detected shutdown (%d of %d)',
306 state.shutdown_count, test.iterations)
Jon Salz4f6c7172012-06-11 20:45:36 +0800307
308 def log_and_update_state(status, error_msg, **kw):
309 self.event_log.Log('rebooted',
310 status=status, error_msg=error_msg, **kw)
311 test.update_state(status=status, error_msg=error_msg)
312
313 if not self.last_shutdown_time:
314 log_and_update_state(status=TestState.FAILED,
315 error_msg='Unable to read shutdown_time')
316 return
317
318 now = time.time()
319 logging.info('%.03f s passed since reboot',
320 now - self.last_shutdown_time)
321
322 if self.last_shutdown_time > now:
323 test.update_state(status=TestState.FAILED,
324 error_msg='Time moved backward during reboot')
325 elif (isinstance(test, factory.RebootStep) and
326 self.test_list.options.max_reboot_time_secs and
327 (now - self.last_shutdown_time >
328 self.test_list.options.max_reboot_time_secs)):
329 # A reboot took too long; fail. (We don't check this for
330 # HaltSteps, because the machine could be halted for a
331 # very long time, and even unplugged with battery backup,
332 # thus hosing the clock.)
333 log_and_update_state(
334 status=TestState.FAILED,
335 error_msg=('More than %d s elapsed during reboot '
336 '(%.03f s, from %s to %s)' % (
337 self.test_list.options.max_reboot_time_secs,
338 now - self.last_shutdown_time,
339 utils.TimeString(self.last_shutdown_time),
340 utils.TimeString(now))),
341 duration=(now-self.last_shutdown_time))
342 elif state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800343 # Good!
Jon Salz4f6c7172012-06-11 20:45:36 +0800344 log_and_update_state(status=TestState.PASSED,
345 duration=(now - self.last_shutdown_time),
346 error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800347 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800348 # Shut down too many times
Jon Salz4f6c7172012-06-11 20:45:36 +0800349 log_and_update_state(status=TestState.FAILED,
350 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800351 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800352 logging.info('Shift keys are depressed; cancelling restarts')
353 # Abort shutdown
Jon Salz4f6c7172012-06-11 20:45:36 +0800354 log_and_update_state(
Jon Salz73e0fd02012-04-04 11:46:38 +0800355 status=TestState.FAILED,
356 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800357 else:
Jon Salz94eb56f2012-06-12 18:01:12 +0800358 def handler():
359 if self._prompt_cancel_shutdown(test, state.shutdown_count + 1):
360 log_and_update_state(
361 status=TestState.FAILED,
362 error_msg='Shutdown aborted by operator')
363 return
364
365 # Time to shutdown again
366 log_and_update_state(
367 status=TestState.ACTIVE,
368 error_msg='',
369 iteration=state.shutdown_count)
370
371 self.event_log.Log('shutdown', operation='reboot')
372 self.state_instance.set_shared_data('shutdown_time',
Jon Salz4f6c7172012-06-11 20:45:36 +0800373 time.time())
Jon Salz94eb56f2012-06-12 18:01:12 +0800374 self.env.shutdown('reboot')
375
376 self.on_ui_startup.append(handler)
377
378 def _prompt_cancel_shutdown(self, test, iteration):
379 if self.options.ui != 'chrome':
380 return False
381
382 pending_shutdown_data = {
383 'delay_secs': test.delay_secs,
384 'time': time.time() + test.delay_secs,
385 'operation': test.operation,
386 'iteration': iteration,
387 'iterations': test.iterations,
388 }
389
390 # Create a new (threaded) event client since we
391 # don't want to use the event loop for this.
392 with EventClient() as event_client:
393 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
394 **pending_shutdown_data))
395 aborted = event_client.wait(
396 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
397 timeout=test.delay_secs) is not None
398 if aborted:
399 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
400 return aborted
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800401
402 def init_states(self):
403 '''
404 Initializes all states on startup.
405 '''
406 for test in self.test_list.get_all_tests():
407 # Make sure the state server knows about all the tests,
408 # defaulting to an untested state.
409 test.update_state(update_parent=False, visible=False)
410
Jon Salzd6361c22012-06-11 22:23:57 +0800411 var_log_messages = None
412
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800413 # Any 'active' tests should be marked as failed now.
414 for test in self.test_list.walk():
415 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800416 if state.status != TestState.ACTIVE:
417 continue
418 if isinstance(test, factory.ShutdownStep):
419 # Shutdown while the test was active - that's good.
420 self.handle_shutdown_complete(test, state)
421 else:
Jon Salzd6361c22012-06-11 22:23:57 +0800422 # Unexpected shutdown. Grab /var/log/messages for context.
423 if var_log_messages is None:
424 try:
425 var_log_messages = (
426 utils.var_log_messages_before_reboot())
427 # Write it to the log, to make it easier to
428 # correlate with /var/log/messages.
429 logging.info(
430 'Unexpected shutdown. '
431 'Tail of /var/log/messages before last reboot:\n'
432 '%s', ('\n'.join(
433 ' ' + x for x in var_log_messages)))
434 except:
435 logging.exception('Unable to grok /var/log/messages')
436 var_log_messages = []
437
438 error_msg = 'Unexpected shutdown while test was running'
439 self.event_log.Log('end_test',
440 path=test.path,
441 status=TestState.FAILED,
442 invocation=test.get_state().invocation,
443 error_msg=error_msg,
444 var_log_messages='\n'.join(var_log_messages))
445 test.update_state(
446 status=TestState.FAILED,
447 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800448
449 def show_next_active_test(self):
450 '''
451 Rotates to the next visible active test.
452 '''
453 self.reap_completed_tests()
454 active_tests = [
455 t for t in self.test_list.walk()
456 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
457 if not active_tests:
458 return
459
460 try:
461 next_test = active_tests[
462 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
463 except ValueError: # visible_test not present in active_tests
464 next_test = active_tests[0]
465
466 self.set_visible_test(next_test)
467
468 def handle_event(self, event):
469 '''
470 Handles an event from the event server.
471 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800472 handler = self.event_handlers.get(event.type)
473 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800474 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800475 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800476 # We don't register handlers for all event types - just ignore
477 # this event.
478 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800479
480 def run_next_test(self):
481 '''
482 Runs the next eligible test (or tests) in self.tests_to_run.
483 '''
484 self.reap_completed_tests()
485 while self.tests_to_run:
486 logging.debug('Tests to run: %s',
487 [x.path for x in self.tests_to_run])
488
489 test = self.tests_to_run[0]
490
491 if test in self.invocations:
492 logging.info('Next test %s is already running', test.path)
493 self.tests_to_run.popleft()
494 return
495
496 if self.invocations and not (test.backgroundable and all(
497 [x.backgroundable for x in self.invocations])):
498 logging.debug('Waiting for non-backgroundable tests to '
499 'complete before running %s', test.path)
500 return
501
502 self.tests_to_run.popleft()
503
Jon Salz74ad3262012-03-16 14:40:55 +0800504 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800505 if os.path.exists(NO_REBOOT_FILE):
506 test.update_state(
507 status=TestState.FAILED, increment_count=1,
508 error_msg=('Skipped shutdown since %s is present' %
509 NO_REBOOT_FILE))
510 continue
511
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800513 error_msg='', shutdown_count=0)
Jon Salz94eb56f2012-06-12 18:01:12 +0800514 if self._prompt_cancel_shutdown(test, 1):
515 self.event_log.Log('reboot_cancelled')
516 test.update_state(
517 status=TestState.FAILED, increment_count=1,
518 error_msg='Shutdown aborted by operator',
519 shutdown_count=0)
520 return
521
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800522 # Save pending test list in the state server
523 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800524 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800525 [t.path for t in self.tests_to_run])
Jon Salz4f6c7172012-06-11 20:45:36 +0800526 # Save shutdown time
527 self.state_instance.set_shared_data('shutdown_time',
528 time.time())
Jon Salz74ad3262012-03-16 14:40:55 +0800529
Jon Salz73e0fd02012-04-04 11:46:38 +0800530 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800531 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800532 shutdown_result = self.env.shutdown(test.operation)
533 if shutdown_result:
534 # That's all, folks!
535 self.run_queue.put(None)
536 return
537 else:
538 # Just pass (e.g., in the chroot).
539 test.update_state(status=TestState.PASSED)
540 self.state_instance.set_shared_data(
541 'tests_after_shutdown', None)
Jon Salz94eb56f2012-06-12 18:01:12 +0800542 # Send event with no fields to indicate that there is no
543 # longer a pending shutdown.
544 self.event_client.post_event(Event(
545 Event.Type.PENDING_SHUTDOWN))
Jon Salz73e0fd02012-04-04 11:46:38 +0800546 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800547
548 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
549 self.invocations[test] = invoc
550 if self.visible_test is None and test.has_ui:
551 self.set_visible_test(test)
552 invoc.start()
553
Jon Salz0405ab52012-03-16 15:26:52 +0800554 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800555 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800556 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800557
558 The tests are run in order unless one fails (then stops).
559 Backgroundable tests are run simultaneously; when a foreground test is
560 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800561
562 @param subtrees: Node or nodes containing tests to run (may either be
563 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800564 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800565 if type(subtrees) != list:
566 subtrees = [subtrees]
567
568 # Nodes we've seen so far, to avoid duplicates.
569 seen = set()
570
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800571 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800572 for subtree in subtrees:
573 for test in subtree.walk():
574 if test in seen:
575 continue
576 seen.add(test)
577
578 if not test.is_leaf():
579 continue
580 if (untested_only and
581 test.get_state().status != TestState.UNTESTED):
582 continue
583 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800584 self.run_next_test()
585
586 def reap_completed_tests(self):
587 '''
588 Removes completed tests from the set of active tests.
589
590 Also updates the visible test if it was reaped.
591 '''
592 for t, v in dict(self.invocations).iteritems():
593 if v.is_completed():
594 del self.invocations[t]
595
596 if (self.visible_test is None or
597 self.visible_test not in self.invocations):
598 self.set_visible_test(None)
599 # Make the first running test, if any, the visible test
600 for t in self.test_list.walk():
601 if t in self.invocations:
602 self.set_visible_test(t)
603 break
604
Hung-Te Lin96632362012-03-20 21:14:18 +0800605 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800606 '''
607 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800608
609 @param abort: True to change state of killed tests to FAILED, False for
610 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800611 '''
612 self.reap_completed_tests()
613 for test, invoc in self.invocations.items():
614 factory.console.info('Killing active test %s...' % test.path)
615 invoc.abort_and_join()
616 factory.console.info('Killed %s' % test.path)
617 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800618 if not abort:
619 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800620 self.reap_completed_tests()
621
Jon Salzf00cdc82012-05-28 18:56:17 +0800622 def stop(self):
623 self.kill_active_tests(False)
624 self.run_tests([])
625
Hung-Te Lin96632362012-03-20 21:14:18 +0800626 def abort_active_tests(self):
627 self.kill_active_tests(True)
628
Jon Salz73e0fd02012-04-04 11:46:38 +0800629 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800630 try:
631 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800632 self.event_log.Log('goofy_init',
633 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800634 except:
635 if self.event_log:
636 try:
Jon Salzb9038572012-05-24 10:34:51 +0800637 self.event_log.Log('goofy_init',
638 success=False,
639 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800640 except:
641 pass
642 raise
643
Jon Salz73e0fd02012-04-04 11:46:38 +0800644 self.run()
645
Jon Salz5f2a0672012-05-22 17:14:06 +0800646 def update_system_info(self):
647 '''Updates system info.'''
Jon Salz49a7d152012-06-19 15:04:09 +0800648 system_info = system.SystemInfo()
Jon Salz5f2a0672012-05-22 17:14:06 +0800649 self.state_instance.set_shared_data('system_info', system_info.__dict__)
650 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
651 system_info=system_info.__dict__))
652 logging.info('System info: %r', system_info.__dict__)
653
Jon Salz37eccbd2012-05-25 16:06:52 +0800654 def update_factory(self):
655 self.kill_active_tests(False)
656 self.run_tests([])
657
658 try:
659 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
660 self.env.shutdown('reboot')
661 except:
662 factory.console.exception('Unable to update')
663
Jon Salz73e0fd02012-04-04 11:46:38 +0800664 def init(self, args=None, env=None):
665 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800666
667 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800668 args: A list of command-line arguments. Uses sys.argv if
669 args is None.
670 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800671 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800672 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800673 parser = OptionParser()
674 parser.add_option('-v', '--verbose', dest='verbose',
675 action='store_true',
676 help='Enable debug logging')
677 parser.add_option('--print_test_list', dest='print_test_list',
678 metavar='FILE',
679 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800680 parser.add_option('--restart', dest='restart',
681 action='store_true',
682 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800683 parser.add_option('--ui', dest='ui', type='choice',
684 choices=['none', 'gtk', 'chrome'],
685 default='gtk',
686 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800687 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
688 type='int', default=1,
689 help=('Factor by which to scale UI '
690 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800691 parser.add_option('--test_list', dest='test_list',
692 metavar='FILE',
693 help='Use FILE as test list')
694 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800695
Jon Salz73e0fd02012-04-04 11:46:38 +0800696 global _inited_logging
697 if not _inited_logging:
698 factory.init_logging('goofy', verbose=self.options.verbose)
699 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800700 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800701
Jon Salz73e0fd02012-04-04 11:46:38 +0800702 if (not suppress_chroot_warning and
703 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800704 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800705 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
706 # That's not going to work! Tell the user how to run
707 # this way.
708 logging.warn(GOOFY_IN_CHROOT_WARNING)
709 time.sleep(1)
710
Jon Salz73e0fd02012-04-04 11:46:38 +0800711 if env:
712 self.env = env
713 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800714 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800715 logging.warn(
716 'Using chroot environment: will not actually run autotests')
717 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800718 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800719 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800720
Jon Salz758e6cc2012-04-03 15:47:07 +0800721 if self.options.restart:
722 state.clear_state()
723
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800724 if self.options.print_test_list:
725 print (factory.read_test_list(self.options.print_test_list).
726 __repr__(recursive=True))
727 return
728
Jon Salz9b312912012-06-04 11:27:00 +0800729 if self.options.ui_scale_factor != 1 and factory.in_qemu():
730 logging.warn(
731 'In QEMU; ignoring ui_scale_factor argument')
732 self.options.ui_scale_factor = 1
733
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800734 logging.info('Started')
735
736 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800737 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800738 self.state_instance.set_shared_data('ui_scale_factor',
739 self.options.ui_scale_factor)
Jon Salz4f6c7172012-06-11 20:45:36 +0800740 self.last_shutdown_time = (
741 self.state_instance.get_shared_data('shutdown_time', optional=True))
742 self.state_instance.del_shared_data('shutdown_time', optional=True)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800743
Jon Salz73e0fd02012-04-04 11:46:38 +0800744 self.options.test_list = (self.options.test_list or find_test_list())
745 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800746 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800747 if not self.state_instance.has_shared_data('ui_lang'):
748 self.state_instance.set_shared_data('ui_lang',
749 self.test_list.options.ui_lang)
Jon Salzdbf398f2012-06-14 17:30:01 +0800750 self.state_instance.set_shared_data(
751 'test_list_options',
752 self.test_list.options.__dict__)
Jon Salz258a40c2012-04-19 12:34:01 +0800753 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800754
755 self.init_states()
756 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800757
Jon Salz5f2a0672012-05-22 17:14:06 +0800758 self.update_system_info()
759
Jon Salz5da61e62012-05-31 13:06:22 +0800760 os.environ['CROS_FACTORY'] = '1'
Jon Salzeebd12e2012-06-08 15:34:56 +0800761 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
Jon Salz5da61e62012-05-31 13:06:22 +0800762
Jon Salzb1b39092012-05-03 02:05:09 +0800763 # Set CROS_UI since some behaviors in ui.py depend on the
764 # particular UI in use. TODO(jsalz): Remove this (and all
765 # places it is used) when the GTK UI is removed.
766 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800767
Jon Salzb1b39092012-05-03 02:05:09 +0800768 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800769 self.env.launch_chrome()
770 logging.info('Waiting for a web socket connection')
771 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800772
773 # Wait for the test widget size to be set; this is done in
774 # an asynchronous RPC so there is a small chance that the
775 # web socket might be opened first.
776 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800777 try:
778 if self.state_instance.get_shared_data('test_widget_size'):
779 break
780 except KeyError:
781 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800782 time.sleep(0.1) # 100 ms
783 else:
784 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800785 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800786 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800787
Jon Salz94eb56f2012-06-12 18:01:12 +0800788 for handler in self.on_ui_startup:
789 handler()
790
Jon Salz8375c2e2012-04-04 15:22:24 +0800791 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800792 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800793
794 def state_change_callback(test, state):
795 self.event_client.post_event(
796 Event(Event.Type.STATE_CHANGE,
797 path=test.path, state=state))
798 self.test_list.state_change_callback = state_change_callback
799
800 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800801 tests_after_shutdown = self.state_instance.get_shared_data(
802 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800803 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800804 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800805
Jon Salz758e6cc2012-04-03 15:47:07 +0800806 if tests_after_shutdown is not None:
807 logging.info('Resuming tests after shutdown: %s',
808 tests_after_shutdown)
809 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800810 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800811 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800812 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800813 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800814 if self.test_list.options.auto_run_on_start:
815 self.run_queue.put(
816 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800817
Jon Salz73e0fd02012-04-04 11:46:38 +0800818 def run(self):
819 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800820 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800821 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800822 pass
823
Jon Salz57717ca2012-04-04 16:47:25 +0800824 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800825 '''Runs all items pending in the event loop.
826
Jon Salz57717ca2012-04-04 16:47:25 +0800827 Args:
828 block: If true, block until at least one event is processed.
829
Jon Salz73e0fd02012-04-04 11:46:38 +0800830 Returns:
831 True to keep going or False to shut down.
832 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800833 events = []
834 if block:
835 # Get at least one event
836 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800837 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800838 try:
839 events.append(self.run_queue.get_nowait())
840 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800841 break
842
Jon Salz73e0fd02012-04-04 11:46:38 +0800843 for event in events:
844 if not event:
845 # Shutdown request.
846 self.run_queue.task_done()
847 return False
848
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800849 try:
850 event()
851 except Exception as e: # pylint: disable=W0703
852 logging.error('Error in event loop: %s', e)
853 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800854 self.record_exception(traceback.format_exception_only(
855 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800856 # But keep going
857 finally:
858 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800859 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800860
Jon Salz258a40c2012-04-19 12:34:01 +0800861 def run_tests_with_status(self, statuses_to_run, starting_at=None,
862 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800863 '''Runs all top-level tests with a particular status.
864
865 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800866
867 Args:
868 starting_at: If provided, only auto-runs tests beginning with
869 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800870 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800871 root = root or self.test_list
872
Jon Salz57717ca2012-04-04 16:47:25 +0800873 if starting_at:
874 # Make sure they passed a test, not a string.
875 assert isinstance(starting_at, factory.FactoryTest)
876
Jon Salz0405ab52012-03-16 15:26:52 +0800877 tests_to_reset = []
878 tests_to_run = []
879
Jon Salz57717ca2012-04-04 16:47:25 +0800880 found_starting_at = False
881
Jon Salz258a40c2012-04-19 12:34:01 +0800882 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800883 if starting_at:
884 if test == starting_at:
885 # We've found starting_at; do auto-run on all
886 # subsequent tests.
887 found_starting_at = True
888 if not found_starting_at:
889 # Don't start this guy yet
890 continue
891
Jon Salz0405ab52012-03-16 15:26:52 +0800892 status = test.get_state().status
893 if status == TestState.ACTIVE or status in statuses_to_run:
894 # Reset the test (later; we will need to abort
895 # all active tests first).
896 tests_to_reset.append(test)
897 if status in statuses_to_run:
898 tests_to_run.append(test)
899
900 self.abort_active_tests()
901
902 # Reset all statuses of the tests to run (in case any tests were active;
903 # we want them to be run again).
904 for test_to_reset in tests_to_reset:
905 for test in test_to_reset.walk():
906 test.update_state(status=TestState.UNTESTED)
907
908 self.run_tests(tests_to_run, untested_only=True)
909
Jon Salz258a40c2012-04-19 12:34:01 +0800910 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800911 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800912 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800913
Jon Salz258a40c2012-04-19 12:34:01 +0800914 self.abort_active_tests()
915 for test in root.walk():
916 test.update_state(status=TestState.UNTESTED)
917 self.run_tests(root)
918
919 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800920 '''"Auto-runs" tests that have not been run yet.
921
922 Args:
923 starting_at: If provide, only auto-runs tests beginning with
924 this test.
925 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800926 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800927 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800928 starting_at=starting_at,
929 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800930
Jon Salz258a40c2012-04-19 12:34:01 +0800931 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800932 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800933 root = root or self.test_list
934 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800935
Jon Salz968e90b2012-03-18 16:12:43 +0800936 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800937 '''Event handler for showing review information screen.
938
939 The information screene is rendered by main UI program (ui.py), so in
940 goofy we only need to kill all active tests, set them as untested, and
941 clear remaining tests.
942 '''
943 self.kill_active_tests(False)
944 self.run_tests([])
945
Jon Salz0405ab52012-03-16 15:26:52 +0800946 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800947 '''Switches to a particular test.
948
949 @param event: The SWITCH_TEST event.
950 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800951 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800952 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800953 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800954 return
955
956 invoc = self.invocations.get(test)
957 if invoc and test.backgroundable:
958 # Already running: just bring to the front if it
959 # has a UI.
960 logging.info('Setting visible test to %s', test.path)
961 self.event_client.post_event(
962 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
963 return
964
965 self.abort_active_tests()
966 for t in test.walk():
967 t.update_state(status=TestState.UNTESTED)
968
969 if self.test_list.options.auto_run_on_keypress:
970 self.auto_run(starting_at=test)
971 else:
972 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800973
Jon Salz73e0fd02012-04-04 11:46:38 +0800974 def wait(self):
975 '''Waits for all pending invocations.
976
977 Useful for testing.
978 '''
979 for k, v in self.invocations.iteritems():
980 logging.info('Waiting for %s to complete...', k)
981 v.thread.join()
982
983 def check_exceptions(self):
984 '''Raises an error if any exceptions have occurred in
985 invocation threads.'''
986 if self.exceptions:
987 raise RuntimeError('Exception in invocation thread: %r' %
988 self.exceptions)
989
990 def record_exception(self, msg):
991 '''Records an exception in an invocation thread.
992
993 An exception with the given message will be rethrown when
994 Goofy is destroyed.'''
995 self.exceptions.append(msg)
996
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800997
998if __name__ == '__main__':
999 Goofy().main()