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