blob: 595e3141b9b43764f9f27e9b7db117bb3fa8df80 [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
Hung-Te Linf2f78f72012-02-08 19:27:11 +08002# -*- coding: utf-8 -*-
3#
Jon Salz37eccbd2012-05-25 16:06:52 +08004# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08005# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8'''
9The main factory flow that runs the factory test and finalizes a device.
10'''
11
Jon Salz0405ab52012-03-16 15:26:52 +080012import logging
13import os
Jon Salz73e0fd02012-04-04 11:46:38 +080014import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080015import subprocess
16import sys
Jon Salz0405ab52012-03-16 15:26:52 +080017import threading
18import time
19import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080020import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080021from collections import deque
22from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080023
Jon Salz0697cbf2012-07-04 15:14:04 +080024import factory_common # pylint: disable=W0611
Jon Salz83591782012-06-26 11:09:58 +080025from cros.factory.goofy.prespawner import Prespawner
26from cros.factory.test import factory
27from cros.factory.test import state
28from cros.factory.test.factory import TestState
29from cros.factory.goofy import updater
Jon Salz51528e12012-07-02 18:54:45 +080030from cros.factory.goofy import test_steps
31from cros.factory.goofy.event_log_watcher import EventLogWatcher
32from cros.factory.test import shopfloor
Jon Salz83591782012-06-26 11:09:58 +080033from cros.factory.test import utils
34from cros.factory.test.event import Event
35from cros.factory.test.event import EventClient
36from cros.factory.test.event import EventServer
37from cros.factory.event_log import EventLog
38from cros.factory.goofy.invocation import TestInvocation
39from cros.factory.goofy import system
40from cros.factory.goofy import test_environment
41from cros.factory.goofy.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080042
43
Jon Salz2f757d42012-06-27 17:06:42 +080044DEFAULT_TEST_LISTS_DIR = os.path.join(factory.FACTORY_PATH, 'test_lists')
45CUSTOM_DIR = os.path.join(factory.FACTORY_PATH, 'custom')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080046HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
47
Jon Salz8796e362012-05-24 11:39:09 +080048# File that suppresses reboot if present (e.g., for development).
49NO_REBOOT_FILE = '/var/log/factory.noreboot'
50
cychiang21886742012-07-05 15:16:32 +080051RUN_QUEUE_TIMEOUT_SECS = 10
52
Jon Salz758e6cc2012-04-03 15:47:07 +080053GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
54You are running Goofy inside the chroot. Autotests are not supported.
55
56To use Goofy in the chroot, first install an Xvnc server:
57
Jon Salz0697cbf2012-07-04 15:14:04 +080058 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080059
60...and then start a VNC X server outside the chroot:
61
Jon Salz0697cbf2012-07-04 15:14:04 +080062 vncserver :10 &
63 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080064
65...and run Goofy as follows:
66
Jon Salz0697cbf2012-07-04 15:14:04 +080067 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080068''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080069suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080070
71def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080072 '''
73 Returns the HWID config tag, or an empty string if none can be found.
74 '''
75 if 'CROS_HWID' in os.environ:
76 return os.environ['CROS_HWID']
77 if os.path.exists(HWID_CFG_PATH):
78 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
79 return hwid_cfg_handle.read().strip()
80 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080081
82
83def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080084 '''
85 Returns the path to the active test list, based on the HWID config tag.
86 '''
87 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080088
Jon Salz0697cbf2012-07-04 15:14:04 +080089 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080090
Jon Salz0697cbf2012-07-04 15:14:04 +080091 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
92 search_files = ['test_list', 'test_list.all']
93 if hwid_cfg:
94 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080095
Jon Salz0697cbf2012-07-04 15:14:04 +080096 for d in search_dirs:
97 for f in search_files:
98 test_list = os.path.join(d, f)
99 if os.path.exists(test_list):
100 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800101
Jon Salz0697cbf2012-07-04 15:14:04 +0800102 logging.warn('Cannot find test lists named any of %s in any of %s',
103 search_files, search_dirs)
104 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800105
Jon Salz73e0fd02012-04-04 11:46:38 +0800106_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800107
108class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800109 '''
110 The main factory flow.
111
112 Note that all methods in this class must be invoked from the main
113 (event) thread. Other threads, such as callbacks and TestInvocation
114 methods, should instead post events on the run queue.
115
116 TODO: Unit tests. (chrome-os-partner:7409)
117
118 Properties:
119 uuid: A unique UUID for this invocation of Goofy.
120 state_instance: An instance of FactoryState.
121 state_server: The FactoryState XML/RPC server.
122 state_server_thread: A thread running state_server.
123 event_server: The EventServer socket server.
124 event_server_thread: A thread running event_server.
125 event_client: A client to the event server.
126 connection_manager: The connection_manager object.
127 network_enabled: Whether the connection_manager is currently
128 enabling connections.
129 ui_process: The factory ui process object.
130 run_queue: A queue of callbacks to invoke from the main thread.
131 invocations: A map from FactoryTest objects to the corresponding
132 TestInvocations objects representing active tests.
133 tests_to_run: A deque of tests that should be run when the current
134 test(s) complete.
135 options: Command-line options.
136 args: Command-line args.
137 test_list: The test list.
138 event_handlers: Map of Event.Type to the method used to handle that
139 event. If the method has an 'event' argument, the event is passed
140 to the handler.
141 exceptions: Exceptions encountered in invocation threads.
142 '''
143 def __init__(self):
144 self.uuid = str(uuid.uuid4())
145 self.state_instance = None
146 self.state_server = None
147 self.state_server_thread = None
148 self.event_server = None
149 self.event_server_thread = None
150 self.event_client = None
151 self.connection_manager = None
152 self.log_watcher = None
153 self.network_enabled = True
154 self.event_log = None
155 self.prespawner = None
156 self.ui_process = None
157 self.run_queue = Queue.Queue()
158 self.invocations = {}
159 self.tests_to_run = deque()
160 self.visible_test = None
161 self.chrome = None
162
163 self.options = None
164 self.args = None
165 self.test_list = None
166 self.on_ui_startup = []
167 self.env = None
168 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800169 self.last_update_check = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800170
Jon Salz85a39882012-07-05 16:45:04 +0800171 def test_or_root(event, parent_or_group=True):
172 '''Returns the test affected by a particular event.
173
174 Args:
175 event: The event containing an optional 'path' attribute.
176 parent_on_group: If True, returns the top-level parent for a test (the
177 root node of the tests that need to be run together if the given test
178 path is to be run).
179 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800180 try:
181 path = event.path
182 except AttributeError:
183 path = None
184
185 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800186 test = self.test_list.lookup_path(path)
187 if parent_or_group:
188 test = test.get_top_level_parent_or_group()
189 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800190 else:
191 return self.test_list
192
193 self.event_handlers = {
194 Event.Type.SWITCH_TEST: self.handle_switch_test,
195 Event.Type.SHOW_NEXT_ACTIVE_TEST:
196 lambda event: self.show_next_active_test(),
197 Event.Type.RESTART_TESTS:
198 lambda event: self.restart_tests(root=test_or_root(event)),
199 Event.Type.AUTO_RUN:
200 lambda event: self.auto_run(root=test_or_root(event)),
201 Event.Type.RE_RUN_FAILED:
202 lambda event: self.re_run_failed(root=test_or_root(event)),
203 Event.Type.RUN_TESTS_WITH_STATUS:
204 lambda event: self.run_tests_with_status(
205 event.status,
206 root=test_or_root(event)),
207 Event.Type.REVIEW:
208 lambda event: self.show_review_information(),
209 Event.Type.UPDATE_SYSTEM_INFO:
210 lambda event: self.update_system_info(),
211 Event.Type.UPDATE_FACTORY:
212 lambda event: self.update_factory(),
213 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800214 lambda event: self.stop(root=test_or_root(event, False),
215 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800216 Event.Type.SET_VISIBLE_TEST:
217 lambda event: self.set_visible_test(
218 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800219 }
220
221 self.exceptions = []
222 self.web_socket_manager = None
223
224 def destroy(self):
225 if self.chrome:
226 self.chrome.kill()
227 self.chrome = None
228 if self.ui_process:
229 utils.kill_process_tree(self.ui_process, 'ui')
230 self.ui_process = None
231 if self.web_socket_manager:
232 logging.info('Stopping web sockets')
233 self.web_socket_manager.close()
234 self.web_socket_manager = None
235 if self.state_server_thread:
236 logging.info('Stopping state server')
237 self.state_server.shutdown()
238 self.state_server_thread.join()
239 self.state_server.server_close()
240 self.state_server_thread = None
241 if self.state_instance:
242 self.state_instance.close()
243 if self.event_server_thread:
244 logging.info('Stopping event server')
245 self.event_server.shutdown() # pylint: disable=E1101
246 self.event_server_thread.join()
247 self.event_server.server_close()
248 self.event_server_thread = None
249 if self.log_watcher:
250 if self.log_watcher.IsThreadStarted():
251 self.log_watcher.StopWatchThread()
252 self.log_watcher = None
253 if self.prespawner:
254 logging.info('Stopping prespawner')
255 self.prespawner.stop()
256 self.prespawner = None
257 if self.event_client:
258 logging.info('Closing event client')
259 self.event_client.close()
260 self.event_client = None
261 if self.event_log:
262 self.event_log.Close()
263 self.event_log = None
264 self.check_exceptions()
265 logging.info('Done destroying Goofy')
266
267 def start_state_server(self):
268 self.state_instance, self.state_server = (
269 state.create_server(bind_address='0.0.0.0'))
270 logging.info('Starting state server')
271 self.state_server_thread = threading.Thread(
272 target=self.state_server.serve_forever,
273 name='StateServer')
274 self.state_server_thread.start()
275
276 def start_event_server(self):
277 self.event_server = EventServer()
278 logging.info('Starting factory event server')
279 self.event_server_thread = threading.Thread(
280 target=self.event_server.serve_forever,
281 name='EventServer') # pylint: disable=E1101
282 self.event_server_thread.start()
283
284 self.event_client = EventClient(
285 callback=self.handle_event, event_loop=self.run_queue)
286
287 self.web_socket_manager = WebSocketManager(self.uuid)
288 self.state_server.add_handler("/event",
289 self.web_socket_manager.handle_web_socket)
290
291 def start_ui(self):
292 ui_proc_args = [
293 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
294 self.options.test_list]
295 if self.options.verbose:
296 ui_proc_args.append('-v')
297 logging.info('Starting ui %s', ui_proc_args)
298 self.ui_process = subprocess.Popen(ui_proc_args)
299 logging.info('Waiting for UI to come up...')
300 self.event_client.wait(
301 lambda event: event.type == Event.Type.UI_READY)
302 logging.info('UI has started')
303
304 def set_visible_test(self, test):
305 if self.visible_test == test:
306 return
307
308 if test:
309 test.update_state(visible=True)
310 if self.visible_test:
311 self.visible_test.update_state(visible=False)
312 self.visible_test = test
313
314 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800315 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800316 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800317
Jon Salz0697cbf2012-07-04 15:14:04 +0800318 @param test: The ShutdownStep.
319 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800320 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800321 test_state = test.update_state(increment_shutdown_count=1)
322 logging.info('Detected shutdown (%d of %d)',
323 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800324
Jon Salz0697cbf2012-07-04 15:14:04 +0800325 def log_and_update_state(status, error_msg, **kw):
326 self.event_log.Log('rebooted',
327 status=status, error_msg=error_msg, **kw)
328 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800329
Jon Salz0697cbf2012-07-04 15:14:04 +0800330 if not self.last_shutdown_time:
331 log_and_update_state(status=TestState.FAILED,
332 error_msg='Unable to read shutdown_time')
333 return
Jon Salz258a40c2012-04-19 12:34:01 +0800334
Jon Salz0697cbf2012-07-04 15:14:04 +0800335 now = time.time()
336 logging.info('%.03f s passed since reboot',
337 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800338
Jon Salz0697cbf2012-07-04 15:14:04 +0800339 if self.last_shutdown_time > now:
340 test.update_state(status=TestState.FAILED,
341 error_msg='Time moved backward during reboot')
342 elif (isinstance(test, factory.RebootStep) and
343 self.test_list.options.max_reboot_time_secs and
344 (now - self.last_shutdown_time >
345 self.test_list.options.max_reboot_time_secs)):
346 # A reboot took too long; fail. (We don't check this for
347 # HaltSteps, because the machine could be halted for a
348 # very long time, and even unplugged with battery backup,
349 # thus hosing the clock.)
350 log_and_update_state(
351 status=TestState.FAILED,
352 error_msg=('More than %d s elapsed during reboot '
353 '(%.03f s, from %s to %s)' % (
354 self.test_list.options.max_reboot_time_secs,
355 now - self.last_shutdown_time,
356 utils.TimeString(self.last_shutdown_time),
357 utils.TimeString(now))),
358 duration=(now-self.last_shutdown_time))
359 elif test_state.shutdown_count == test.iterations:
360 # Good!
361 log_and_update_state(status=TestState.PASSED,
362 duration=(now - self.last_shutdown_time),
363 error_msg='')
364 elif test_state.shutdown_count > test.iterations:
365 # Shut down too many times
366 log_and_update_state(status=TestState.FAILED,
367 error_msg='Too many shutdowns')
368 elif utils.are_shift_keys_depressed():
369 logging.info('Shift keys are depressed; cancelling restarts')
370 # Abort shutdown
371 log_and_update_state(
372 status=TestState.FAILED,
373 error_msg='Shutdown aborted with double shift keys')
374 else:
375 def handler():
376 if self._prompt_cancel_shutdown(
377 test, test_state.shutdown_count + 1):
378 log_and_update_state(
379 status=TestState.FAILED,
380 error_msg='Shutdown aborted by operator')
381 return
Jon Salz0405ab52012-03-16 15:26:52 +0800382
Jon Salz0697cbf2012-07-04 15:14:04 +0800383 # Time to shutdown again
384 log_and_update_state(
385 status=TestState.ACTIVE,
386 error_msg='',
387 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800388
Jon Salz0697cbf2012-07-04 15:14:04 +0800389 self.event_log.Log('shutdown', operation='reboot')
390 self.state_instance.set_shared_data('shutdown_time',
391 time.time())
392 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800393
Jon Salz0697cbf2012-07-04 15:14:04 +0800394 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800395
Jon Salz0697cbf2012-07-04 15:14:04 +0800396 def _prompt_cancel_shutdown(self, test, iteration):
397 if self.options.ui != 'chrome':
398 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800399
Jon Salz0697cbf2012-07-04 15:14:04 +0800400 pending_shutdown_data = {
401 'delay_secs': test.delay_secs,
402 'time': time.time() + test.delay_secs,
403 'operation': test.operation,
404 'iteration': iteration,
405 'iterations': test.iterations,
406 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800407
Jon Salz0697cbf2012-07-04 15:14:04 +0800408 # Create a new (threaded) event client since we
409 # don't want to use the event loop for this.
410 with EventClient() as event_client:
411 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
412 **pending_shutdown_data))
413 aborted = event_client.wait(
414 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
415 timeout=test.delay_secs) is not None
416 if aborted:
417 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
418 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800419
Jon Salz0697cbf2012-07-04 15:14:04 +0800420 def init_states(self):
421 '''
422 Initializes all states on startup.
423 '''
424 for test in self.test_list.get_all_tests():
425 # Make sure the state server knows about all the tests,
426 # defaulting to an untested state.
427 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800428
Jon Salz0697cbf2012-07-04 15:14:04 +0800429 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800430
Jon Salz0697cbf2012-07-04 15:14:04 +0800431 # Any 'active' tests should be marked as failed now.
432 for test in self.test_list.walk():
433 test_state = test.get_state()
434 if test_state.status != TestState.ACTIVE:
435 continue
436 if isinstance(test, factory.ShutdownStep):
437 # Shutdown while the test was active - that's good.
438 self.handle_shutdown_complete(test, test_state)
439 else:
440 # Unexpected shutdown. Grab /var/log/messages for context.
441 if var_log_messages is None:
442 try:
443 var_log_messages = (
444 utils.var_log_messages_before_reboot())
445 # Write it to the log, to make it easier to
446 # correlate with /var/log/messages.
447 logging.info(
448 'Unexpected shutdown. '
449 'Tail of /var/log/messages before last reboot:\n'
450 '%s', ('\n'.join(
451 ' ' + x for x in var_log_messages)))
452 except: # pylint: disable=W0702
453 logging.exception('Unable to grok /var/log/messages')
454 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800455
Jon Salz0697cbf2012-07-04 15:14:04 +0800456 error_msg = 'Unexpected shutdown while test was running'
457 self.event_log.Log('end_test',
458 path=test.path,
459 status=TestState.FAILED,
460 invocation=test.get_state().invocation,
461 error_msg=error_msg,
462 var_log_messages='\n'.join(var_log_messages))
463 test.update_state(
464 status=TestState.FAILED,
465 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800466
Jon Salz0697cbf2012-07-04 15:14:04 +0800467 def show_next_active_test(self):
468 '''
469 Rotates to the next visible active test.
470 '''
471 self.reap_completed_tests()
472 active_tests = [
473 t for t in self.test_list.walk()
474 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
475 if not active_tests:
476 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800477
Jon Salz0697cbf2012-07-04 15:14:04 +0800478 try:
479 next_test = active_tests[
480 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
481 except ValueError: # visible_test not present in active_tests
482 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800483
Jon Salz0697cbf2012-07-04 15:14:04 +0800484 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800485
Jon Salz0697cbf2012-07-04 15:14:04 +0800486 def handle_event(self, event):
487 '''
488 Handles an event from the event server.
489 '''
490 handler = self.event_handlers.get(event.type)
491 if handler:
492 handler(event)
493 else:
494 # We don't register handlers for all event types - just ignore
495 # this event.
496 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800497
Jon Salz0697cbf2012-07-04 15:14:04 +0800498 def run_next_test(self):
499 '''
500 Runs the next eligible test (or tests) in self.tests_to_run.
501 '''
502 self.reap_completed_tests()
503 while self.tests_to_run:
504 logging.debug('Tests to run: %s',
505 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800506
Jon Salz0697cbf2012-07-04 15:14:04 +0800507 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800508
Jon Salz0697cbf2012-07-04 15:14:04 +0800509 if test in self.invocations:
510 logging.info('Next test %s is already running', test.path)
511 self.tests_to_run.popleft()
512 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800513
Jon Salz304a75d2012-07-06 11:14:15 +0800514 for i in test.require_run:
515 for j in i.walk():
516 if j.get_state().status == TestState.ACTIVE:
517 logging.info('Waiting for active test %s to complete '
518 'before running %s', j.path, test.path)
519 return
520
Jon Salz0697cbf2012-07-04 15:14:04 +0800521 if self.invocations and not (test.backgroundable and all(
522 [x.backgroundable for x in self.invocations])):
523 logging.debug('Waiting for non-backgroundable tests to '
524 'complete before running %s', test.path)
525 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800526
Jon Salz0697cbf2012-07-04 15:14:04 +0800527 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800528
Jon Salz304a75d2012-07-06 11:14:15 +0800529 untested = set()
530 for i in test.require_run:
531 for j in i.walk():
532 if j == test:
533 # We've hit this test itself; stop checking
534 break
535 if j.get_state().status == TestState.UNTESTED:
536 # Found an untested test; move on to the next
537 # element in require_run.
538 untested.add(j)
539 break
540
541 if untested:
542 untested_paths = ', '.join(sorted([x.path for x in untested]))
543 if self.state_instance.get_shared_data('engineering_mode',
544 optional=True):
545 # In engineering mode, we'll let it go.
546 factory.console.warn('In engineering mode; running '
547 '%s even though required tests '
548 '[%s] have not completed',
549 test.path, untested_paths)
550 else:
551 # Not in engineering mode; mark it failed.
552 error_msg = ('Required tests [%s] have not been run yet'
553 % untested_paths)
554 factory.console.error('Not running %s: %s',
555 test.path, error_msg)
556 test.update_state(status=TestState.FAILED,
557 error_msg=error_msg)
558 continue
559
Jon Salz0697cbf2012-07-04 15:14:04 +0800560 if isinstance(test, factory.ShutdownStep):
561 if os.path.exists(NO_REBOOT_FILE):
562 test.update_state(
563 status=TestState.FAILED, increment_count=1,
564 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800565 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800566 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800567
Jon Salz0697cbf2012-07-04 15:14:04 +0800568 test.update_state(status=TestState.ACTIVE, increment_count=1,
569 error_msg='', shutdown_count=0)
570 if self._prompt_cancel_shutdown(test, 1):
571 self.event_log.Log('reboot_cancelled')
572 test.update_state(
573 status=TestState.FAILED, increment_count=1,
574 error_msg='Shutdown aborted by operator',
575 shutdown_count=0)
576 return
Jon Salz2f757d42012-06-27 17:06:42 +0800577
Jon Salz0697cbf2012-07-04 15:14:04 +0800578 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800579 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800580 'tests_after_shutdown',
581 [t.path for t in self.tests_to_run])
582 # Save shutdown time
583 self.state_instance.set_shared_data('shutdown_time',
584 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800585
Jon Salz0697cbf2012-07-04 15:14:04 +0800586 with self.env.lock:
587 self.event_log.Log('shutdown', operation=test.operation)
588 shutdown_result = self.env.shutdown(test.operation)
589 if shutdown_result:
590 # That's all, folks!
591 self.run_queue.put(None)
592 return
593 else:
594 # Just pass (e.g., in the chroot).
595 test.update_state(status=TestState.PASSED)
596 self.state_instance.set_shared_data(
597 'tests_after_shutdown', None)
598 # Send event with no fields to indicate that there is no
599 # longer a pending shutdown.
600 self.event_client.post_event(Event(
601 Event.Type.PENDING_SHUTDOWN))
602 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800603
Jon Salz0697cbf2012-07-04 15:14:04 +0800604 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
605 self.invocations[test] = invoc
606 if self.visible_test is None and test.has_ui:
607 self.set_visible_test(test)
608 self.check_connection_manager()
609 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800610
Jon Salz0697cbf2012-07-04 15:14:04 +0800611 def check_connection_manager(self):
612 exclusive_tests = [
613 test.path
614 for test in self.invocations
615 if test.is_exclusive(
616 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
617 if exclusive_tests:
618 # Make sure networking is disabled.
619 if self.network_enabled:
620 logging.info('Disabling network, as requested by %s',
621 exclusive_tests)
622 self.connection_manager.DisableNetworking()
623 self.network_enabled = False
624 else:
625 # Make sure networking is enabled.
626 if not self.network_enabled:
627 logging.info('Re-enabling network')
628 self.connection_manager.EnableNetworking()
629 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800630
cychiang21886742012-07-05 15:16:32 +0800631 def check_for_updates(self):
632 '''
633 Schedules an asynchronous check for updates if necessary.
634 '''
635 if not self.test_list.options.update_period_secs:
636 # Not enabled.
637 return
638
639 now = time.time()
640 if self.last_update_check and (
641 now - self.last_update_check <
642 self.test_list.options.update_period_secs):
643 # Not yet time for another check.
644 return
645
646 self.last_update_check = now
647
648 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
649 if reached_shopfloor:
650 new_update_md5sum = md5sum if needs_update else None
651 if system.SystemInfo.update_md5sum != new_update_md5sum:
652 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
653 system.SystemInfo.update_md5sum = new_update_md5sum
654 self.run_queue.put(self.update_system_info)
655
656 updater.CheckForUpdateAsync(
657 handle_check_for_update,
658 self.test_list.options.shopfloor_timeout_secs)
659
Jon Salz0697cbf2012-07-04 15:14:04 +0800660 def run_tests(self, subtrees, untested_only=False):
661 '''
662 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800663
Jon Salz0697cbf2012-07-04 15:14:04 +0800664 The tests are run in order unless one fails (then stops).
665 Backgroundable tests are run simultaneously; when a foreground test is
666 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800667
Jon Salz0697cbf2012-07-04 15:14:04 +0800668 @param subtrees: Node or nodes containing tests to run (may either be
669 a single test or a list). Duplicates will be ignored.
670 '''
671 if type(subtrees) != list:
672 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800673
Jon Salz0697cbf2012-07-04 15:14:04 +0800674 # Nodes we've seen so far, to avoid duplicates.
675 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800676
Jon Salz0697cbf2012-07-04 15:14:04 +0800677 self.tests_to_run = deque()
678 for subtree in subtrees:
679 for test in subtree.walk():
680 if test in seen:
681 continue
682 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800683
Jon Salz0697cbf2012-07-04 15:14:04 +0800684 if not test.is_leaf():
685 continue
686 if (untested_only and
687 test.get_state().status != TestState.UNTESTED):
688 continue
689 self.tests_to_run.append(test)
690 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800691
Jon Salz0697cbf2012-07-04 15:14:04 +0800692 def reap_completed_tests(self):
693 '''
694 Removes completed tests from the set of active tests.
695
696 Also updates the visible test if it was reaped.
697 '''
698 for t, v in dict(self.invocations).iteritems():
699 if v.is_completed():
700 del self.invocations[t]
701
702 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800703 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800704 self.set_visible_test(None)
705 # Make the first running test, if any, the visible test
706 for t in self.test_list.walk():
707 if t in self.invocations:
708 self.set_visible_test(t)
709 break
710
Jon Salz85a39882012-07-05 16:45:04 +0800711 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800712 '''
713 Kills and waits for all active tests.
714
Jon Salz85a39882012-07-05 16:45:04 +0800715 Args:
716 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800717 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800718 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800719 '''
720 self.reap_completed_tests()
721 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800722 if root and not test.has_ancestor(root):
723 continue
724
Jon Salz0697cbf2012-07-04 15:14:04 +0800725 factory.console.info('Killing active test %s...' % test.path)
726 invoc.abort_and_join()
727 factory.console.info('Killed %s' % test.path)
728 del self.invocations[test]
729 if not abort:
730 test.update_state(status=TestState.UNTESTED)
731 self.reap_completed_tests()
732
Jon Salz85a39882012-07-05 16:45:04 +0800733 def stop(self, root=None, fail=False):
734 self.kill_active_tests(fail, root)
735 # Remove any tests in the run queue under the root.
736 self.tests_to_run = deque([x for x in self.tests_to_run
737 if root and not x.has_ancestor(root)])
738 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800739
740 def abort_active_tests(self):
741 self.kill_active_tests(True)
742
743 def main(self):
744 try:
745 self.init()
746 self.event_log.Log('goofy_init',
747 success=True)
748 except:
749 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800750 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800751 self.event_log.Log('goofy_init',
752 success=False,
753 trace=traceback.format_exc())
754 except: # pylint: disable=W0702
755 pass
756 raise
757
758 self.run()
759
760 def update_system_info(self):
761 '''Updates system info.'''
762 system_info = system.SystemInfo()
763 self.state_instance.set_shared_data('system_info', system_info.__dict__)
764 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
765 system_info=system_info.__dict__))
766 logging.info('System info: %r', system_info.__dict__)
767
768 def update_factory(self):
769 self.kill_active_tests(False)
770 self.run_tests([])
771
772 try:
773 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
774 self.env.shutdown('reboot')
775 except: # pylint: disable=W0702
776 factory.console.exception('Unable to update')
777
778 def init(self, args=None, env=None):
779 '''Initializes Goofy.
780
781 Args:
782 args: A list of command-line arguments. Uses sys.argv if
783 args is None.
784 env: An Environment instance to use (or None to choose
785 FakeChrootEnvironment or DUTEnvironment as appropriate).
786 '''
787 parser = OptionParser()
788 parser.add_option('-v', '--verbose', dest='verbose',
789 action='store_true',
790 help='Enable debug logging')
791 parser.add_option('--print_test_list', dest='print_test_list',
792 metavar='FILE',
793 help='Read and print test list FILE, and exit')
794 parser.add_option('--restart', dest='restart',
795 action='store_true',
796 help='Clear all test state')
797 parser.add_option('--ui', dest='ui', type='choice',
798 choices=['none', 'gtk', 'chrome'],
Jon Salz06f2e852012-07-06 18:37:58 +0800799 default=('chrome' if utils.in_chroot() else 'gtk'),
Jon Salz0697cbf2012-07-04 15:14:04 +0800800 help='UI to use')
801 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
802 type='int', default=1,
803 help=('Factor by which to scale UI '
804 '(Chrome UI only)'))
805 parser.add_option('--test_list', dest='test_list',
806 metavar='FILE',
807 help='Use FILE as test list')
808 (self.options, self.args) = parser.parse_args(args)
809
Jon Salz46b89562012-07-05 11:49:22 +0800810 # Make sure factory directories exist.
811 factory.get_log_root()
812 factory.get_state_root()
813 factory.get_test_data_root()
814
Jon Salz0697cbf2012-07-04 15:14:04 +0800815 global _inited_logging # pylint: disable=W0603
816 if not _inited_logging:
817 factory.init_logging('goofy', verbose=self.options.verbose)
818 _inited_logging = True
819 self.event_log = EventLog('goofy')
820
821 if (not suppress_chroot_warning and
822 factory.in_chroot() and
823 self.options.ui == 'gtk' and
824 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
825 # That's not going to work! Tell the user how to run
826 # this way.
827 logging.warn(GOOFY_IN_CHROOT_WARNING)
828 time.sleep(1)
829
830 if env:
831 self.env = env
832 elif factory.in_chroot():
833 self.env = test_environment.FakeChrootEnvironment()
834 logging.warn(
835 'Using chroot environment: will not actually run autotests')
836 else:
837 self.env = test_environment.DUTEnvironment()
838 self.env.goofy = self
839
840 if self.options.restart:
841 state.clear_state()
842
843 if self.options.print_test_list:
844 print (factory.read_test_list(
845 self.options.print_test_list,
846 test_classes=dict(test_steps.__dict__)).
847 __repr__(recursive=True))
848 return
849
850 if self.options.ui_scale_factor != 1 and utils.in_qemu():
851 logging.warn(
852 'In QEMU; ignoring ui_scale_factor argument')
853 self.options.ui_scale_factor = 1
854
855 logging.info('Started')
856
857 self.start_state_server()
858 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
859 self.state_instance.set_shared_data('ui_scale_factor',
860 self.options.ui_scale_factor)
861 self.last_shutdown_time = (
862 self.state_instance.get_shared_data('shutdown_time', optional=True))
863 self.state_instance.del_shared_data('shutdown_time', optional=True)
864
865 if not self.options.test_list:
866 self.options.test_list = find_test_list()
867 if not self.options.test_list:
868 logging.error('No test list. Aborting.')
869 sys.exit(1)
870 logging.info('Using test list %s', self.options.test_list)
871
872 self.test_list = factory.read_test_list(
873 self.options.test_list,
874 self.state_instance,
875 test_classes=dict(test_steps.__dict__))
876 if not self.state_instance.has_shared_data('ui_lang'):
877 self.state_instance.set_shared_data('ui_lang',
878 self.test_list.options.ui_lang)
879 self.state_instance.set_shared_data(
880 'test_list_options',
881 self.test_list.options.__dict__)
882 self.state_instance.test_list = self.test_list
883
884 self.init_states()
885 self.start_event_server()
886 self.connection_manager = self.env.create_connection_manager(
887 self.test_list.options.wlans)
888 # Note that we create a log watcher even if
889 # sync_event_log_period_secs isn't set (no background
890 # syncing), since we may use it to flush event logs as well.
891 self.log_watcher = EventLogWatcher(
892 self.test_list.options.sync_event_log_period_secs,
893 handle_event_logs_callback=self._handle_event_logs)
894 if self.test_list.options.sync_event_log_period_secs:
895 self.log_watcher.StartWatchThread()
896
897 self.update_system_info()
898
899 os.environ['CROS_FACTORY'] = '1'
900 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
901
902 # Set CROS_UI since some behaviors in ui.py depend on the
903 # particular UI in use. TODO(jsalz): Remove this (and all
904 # places it is used) when the GTK UI is removed.
905 os.environ['CROS_UI'] = self.options.ui
906
907 if self.options.ui == 'chrome':
908 self.env.launch_chrome()
909 logging.info('Waiting for a web socket connection')
910 self.web_socket_manager.wait()
911
912 # Wait for the test widget size to be set; this is done in
913 # an asynchronous RPC so there is a small chance that the
914 # web socket might be opened first.
915 for _ in range(100): # 10 s
916 try:
917 if self.state_instance.get_shared_data('test_widget_size'):
918 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800919 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800920 pass # Retry
921 time.sleep(0.1) # 100 ms
922 else:
923 logging.warn('Never received test_widget_size from UI')
924 elif self.options.ui == 'gtk':
925 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800926
Jon Salz0697cbf2012-07-04 15:14:04 +0800927 for handler in self.on_ui_startup:
928 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800929
Jon Salz0697cbf2012-07-04 15:14:04 +0800930 self.prespawner = Prespawner()
931 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800932
Jon Salz0697cbf2012-07-04 15:14:04 +0800933 def state_change_callback(test, test_state):
934 self.event_client.post_event(
935 Event(Event.Type.STATE_CHANGE,
936 path=test.path, state=test_state))
937 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800938
Jon Salz0697cbf2012-07-04 15:14:04 +0800939 try:
940 tests_after_shutdown = self.state_instance.get_shared_data(
941 'tests_after_shutdown')
942 except KeyError:
943 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800944
Jon Salz0697cbf2012-07-04 15:14:04 +0800945 if tests_after_shutdown is not None:
946 logging.info('Resuming tests after shutdown: %s',
947 tests_after_shutdown)
948 self.state_instance.set_shared_data('tests_after_shutdown', None)
949 self.tests_to_run.extend(
950 self.test_list.lookup_path(t) for t in tests_after_shutdown)
951 self.run_queue.put(self.run_next_test)
952 else:
953 if self.test_list.options.auto_run_on_start:
954 self.run_queue.put(
955 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800956
Jon Salz0697cbf2012-07-04 15:14:04 +0800957 def run(self):
958 '''Runs Goofy.'''
959 # Process events forever.
960 while self.run_once(True):
961 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800962
Jon Salz0697cbf2012-07-04 15:14:04 +0800963 def run_once(self, block=False):
964 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800965
Jon Salz0697cbf2012-07-04 15:14:04 +0800966 Args:
967 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800968
Jon Salz0697cbf2012-07-04 15:14:04 +0800969 Returns:
970 True to keep going or False to shut down.
971 '''
972 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +0800973 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +0800974 # Nothing on the run queue.
975 self._run_queue_idle()
976 if block:
977 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +0800978 try:
979 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
980 except Queue.Empty:
981 # Keep going (calling _run_queue_idle() again at the top of
982 # the loop)
983 continue
Jon Salz0697cbf2012-07-04 15:14:04 +0800984 # ...and grab anything else that showed up at the same
985 # time.
986 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +0800987 else:
988 break
Jon Salz51528e12012-07-02 18:54:45 +0800989
Jon Salz0697cbf2012-07-04 15:14:04 +0800990 for event in events:
991 if not event:
992 # Shutdown request.
993 self.run_queue.task_done()
994 return False
Jon Salz51528e12012-07-02 18:54:45 +0800995
Jon Salz0697cbf2012-07-04 15:14:04 +0800996 try:
997 event()
Jon Salz85a39882012-07-05 16:45:04 +0800998 except: # pylint: disable=W0702
999 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001000 self.record_exception(traceback.format_exception_only(
1001 *sys.exc_info()[:2]))
1002 # But keep going
1003 finally:
1004 self.run_queue.task_done()
1005 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001006
Jon Salz0697cbf2012-07-04 15:14:04 +08001007 def _run_queue_idle(self):
1008 '''Invoked when the run queue has no events.'''
1009 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001010 self.check_for_updates()
Jon Salz57717ca2012-04-04 16:47:25 +08001011
Jon Salz0697cbf2012-07-04 15:14:04 +08001012 def _handle_event_logs(self, log_name, chunk):
1013 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001014
Jon Salz0697cbf2012-07-04 15:14:04 +08001015 Attempts to upload the event logs to the shopfloor server.
1016 '''
1017 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1018 start_time = time.time()
1019 logging.info('Syncing %s', description)
1020 shopfloor_client = shopfloor.get_instance(
1021 detect=True,
1022 timeout=self.test_list.options.shopfloor_timeout_secs)
1023 shopfloor_client.UploadEvent(log_name, chunk)
1024 logging.info(
1025 'Successfully synced %s in %.03f s',
1026 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001027
Jon Salz0697cbf2012-07-04 15:14:04 +08001028 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1029 root=None):
1030 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001031
Jon Salz0697cbf2012-07-04 15:14:04 +08001032 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001033
Jon Salz0697cbf2012-07-04 15:14:04 +08001034 Args:
1035 starting_at: If provided, only auto-runs tests beginning with
1036 this test.
1037 '''
1038 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001039
Jon Salz0697cbf2012-07-04 15:14:04 +08001040 if starting_at:
1041 # Make sure they passed a test, not a string.
1042 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001043
Jon Salz0697cbf2012-07-04 15:14:04 +08001044 tests_to_reset = []
1045 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001046
Jon Salz0697cbf2012-07-04 15:14:04 +08001047 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001048
Jon Salz0697cbf2012-07-04 15:14:04 +08001049 for test in root.get_top_level_tests():
1050 if starting_at:
1051 if test == starting_at:
1052 # We've found starting_at; do auto-run on all
1053 # subsequent tests.
1054 found_starting_at = True
1055 if not found_starting_at:
1056 # Don't start this guy yet
1057 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001058
Jon Salz0697cbf2012-07-04 15:14:04 +08001059 status = test.get_state().status
1060 if status == TestState.ACTIVE or status in statuses_to_run:
1061 # Reset the test (later; we will need to abort
1062 # all active tests first).
1063 tests_to_reset.append(test)
1064 if status in statuses_to_run:
1065 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001066
Jon Salz0697cbf2012-07-04 15:14:04 +08001067 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001068
Jon Salz0697cbf2012-07-04 15:14:04 +08001069 # Reset all statuses of the tests to run (in case any tests were active;
1070 # we want them to be run again).
1071 for test_to_reset in tests_to_reset:
1072 for test in test_to_reset.walk():
1073 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001074
Jon Salz0697cbf2012-07-04 15:14:04 +08001075 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001076
Jon Salz0697cbf2012-07-04 15:14:04 +08001077 def restart_tests(self, root=None):
1078 '''Restarts all tests.'''
1079 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001080
Jon Salz0697cbf2012-07-04 15:14:04 +08001081 self.abort_active_tests()
1082 for test in root.walk():
1083 test.update_state(status=TestState.UNTESTED)
1084 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001085
Jon Salz0697cbf2012-07-04 15:14:04 +08001086 def auto_run(self, starting_at=None, root=None):
1087 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001088
Jon Salz0697cbf2012-07-04 15:14:04 +08001089 Args:
1090 starting_at: If provide, only auto-runs tests beginning with
1091 this test.
1092 '''
1093 root = root or self.test_list
1094 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1095 starting_at=starting_at,
1096 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001097
Jon Salz0697cbf2012-07-04 15:14:04 +08001098 def re_run_failed(self, root=None):
1099 '''Re-runs failed tests.'''
1100 root = root or self.test_list
1101 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001102
Jon Salz0697cbf2012-07-04 15:14:04 +08001103 def show_review_information(self):
1104 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001105
Jon Salz0697cbf2012-07-04 15:14:04 +08001106 The information screene is rendered by main UI program (ui.py), so in
1107 goofy we only need to kill all active tests, set them as untested, and
1108 clear remaining tests.
1109 '''
1110 self.kill_active_tests(False)
1111 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001112
Jon Salz0697cbf2012-07-04 15:14:04 +08001113 def handle_switch_test(self, event):
1114 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001115
Jon Salz0697cbf2012-07-04 15:14:04 +08001116 @param event: The SWITCH_TEST event.
1117 '''
1118 test = self.test_list.lookup_path(event.path)
1119 if not test:
1120 logging.error('Unknown test %r', event.key)
1121 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001122
Jon Salz0697cbf2012-07-04 15:14:04 +08001123 invoc = self.invocations.get(test)
1124 if invoc and test.backgroundable:
1125 # Already running: just bring to the front if it
1126 # has a UI.
1127 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001128 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001129 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001130
Jon Salz0697cbf2012-07-04 15:14:04 +08001131 self.abort_active_tests()
1132 for t in test.walk():
1133 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001134
Jon Salz0697cbf2012-07-04 15:14:04 +08001135 if self.test_list.options.auto_run_on_keypress:
1136 self.auto_run(starting_at=test)
1137 else:
1138 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001139
Jon Salz0697cbf2012-07-04 15:14:04 +08001140 def wait(self):
1141 '''Waits for all pending invocations.
1142
1143 Useful for testing.
1144 '''
1145 for k, v in self.invocations.iteritems():
1146 logging.info('Waiting for %s to complete...', k)
1147 v.thread.join()
1148
1149 def check_exceptions(self):
1150 '''Raises an error if any exceptions have occurred in
1151 invocation threads.'''
1152 if self.exceptions:
1153 raise RuntimeError('Exception in invocation thread: %r' %
1154 self.exceptions)
1155
1156 def record_exception(self, msg):
1157 '''Records an exception in an invocation thread.
1158
1159 An exception with the given message will be rethrown when
1160 Goofy is destroyed.'''
1161 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001162
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001163
1164if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001165 Goofy().main()