blob: 2e99bdd79d5ace762a5793964633bc6629498001 [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
Jon Salz5c344f62012-07-13 14:31:16 +080051# Value for tests_after_shutdown that forces auto-run (e.g., after
52# a factory update, when the available set of tests might change).
53FORCE_AUTO_RUN = 'force_auto_run'
54
cychiang21886742012-07-05 15:16:32 +080055RUN_QUEUE_TIMEOUT_SECS = 10
56
Jon Salz758e6cc2012-04-03 15:47:07 +080057GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
58You are running Goofy inside the chroot. Autotests are not supported.
59
60To use Goofy in the chroot, first install an Xvnc server:
61
Jon Salz0697cbf2012-07-04 15:14:04 +080062 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080063
64...and then start a VNC X server outside the chroot:
65
Jon Salz0697cbf2012-07-04 15:14:04 +080066 vncserver :10 &
67 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080068
69...and run Goofy as follows:
70
Jon Salz0697cbf2012-07-04 15:14:04 +080071 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080072''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080073suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080074
75def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080076 '''
77 Returns the HWID config tag, or an empty string if none can be found.
78 '''
79 if 'CROS_HWID' in os.environ:
80 return os.environ['CROS_HWID']
81 if os.path.exists(HWID_CFG_PATH):
82 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
83 return hwid_cfg_handle.read().strip()
84 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080085
86
87def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080088 '''
89 Returns the path to the active test list, based on the HWID config tag.
90 '''
91 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080092
Jon Salz0697cbf2012-07-04 15:14:04 +080093 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080094
Jon Salz0697cbf2012-07-04 15:14:04 +080095 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
96 search_files = ['test_list', 'test_list.all']
97 if hwid_cfg:
98 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080099
Jon Salz0697cbf2012-07-04 15:14:04 +0800100 for d in search_dirs:
101 for f in search_files:
102 test_list = os.path.join(d, f)
103 if os.path.exists(test_list):
104 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800105
Jon Salz0697cbf2012-07-04 15:14:04 +0800106 logging.warn('Cannot find test lists named any of %s in any of %s',
107 search_files, search_dirs)
108 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800109
Jon Salz73e0fd02012-04-04 11:46:38 +0800110_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800111
112class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800113 '''
114 The main factory flow.
115
116 Note that all methods in this class must be invoked from the main
117 (event) thread. Other threads, such as callbacks and TestInvocation
118 methods, should instead post events on the run queue.
119
120 TODO: Unit tests. (chrome-os-partner:7409)
121
122 Properties:
123 uuid: A unique UUID for this invocation of Goofy.
124 state_instance: An instance of FactoryState.
125 state_server: The FactoryState XML/RPC server.
126 state_server_thread: A thread running state_server.
127 event_server: The EventServer socket server.
128 event_server_thread: A thread running event_server.
129 event_client: A client to the event server.
130 connection_manager: The connection_manager object.
131 network_enabled: Whether the connection_manager is currently
132 enabling connections.
133 ui_process: The factory ui process object.
134 run_queue: A queue of callbacks to invoke from the main thread.
135 invocations: A map from FactoryTest objects to the corresponding
136 TestInvocations objects representing active tests.
137 tests_to_run: A deque of tests that should be run when the current
138 test(s) complete.
139 options: Command-line options.
140 args: Command-line args.
141 test_list: The test list.
142 event_handlers: Map of Event.Type to the method used to handle that
143 event. If the method has an 'event' argument, the event is passed
144 to the handler.
145 exceptions: Exceptions encountered in invocation threads.
146 '''
147 def __init__(self):
148 self.uuid = str(uuid.uuid4())
149 self.state_instance = None
150 self.state_server = None
151 self.state_server_thread = None
152 self.event_server = None
153 self.event_server_thread = None
154 self.event_client = None
155 self.connection_manager = None
156 self.log_watcher = None
157 self.network_enabled = True
158 self.event_log = None
159 self.prespawner = None
160 self.ui_process = None
161 self.run_queue = Queue.Queue()
162 self.invocations = {}
163 self.tests_to_run = deque()
164 self.visible_test = None
165 self.chrome = None
166
167 self.options = None
168 self.args = None
169 self.test_list = None
170 self.on_ui_startup = []
171 self.env = None
172 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800173 self.last_update_check = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800174
Jon Salz85a39882012-07-05 16:45:04 +0800175 def test_or_root(event, parent_or_group=True):
176 '''Returns the test affected by a particular event.
177
178 Args:
179 event: The event containing an optional 'path' attribute.
180 parent_on_group: If True, returns the top-level parent for a test (the
181 root node of the tests that need to be run together if the given test
182 path is to be run).
183 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800184 try:
185 path = event.path
186 except AttributeError:
187 path = None
188
189 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800190 test = self.test_list.lookup_path(path)
191 if parent_or_group:
192 test = test.get_top_level_parent_or_group()
193 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800194 else:
195 return self.test_list
196
197 self.event_handlers = {
198 Event.Type.SWITCH_TEST: self.handle_switch_test,
199 Event.Type.SHOW_NEXT_ACTIVE_TEST:
200 lambda event: self.show_next_active_test(),
201 Event.Type.RESTART_TESTS:
202 lambda event: self.restart_tests(root=test_or_root(event)),
203 Event.Type.AUTO_RUN:
204 lambda event: self.auto_run(root=test_or_root(event)),
205 Event.Type.RE_RUN_FAILED:
206 lambda event: self.re_run_failed(root=test_or_root(event)),
207 Event.Type.RUN_TESTS_WITH_STATUS:
208 lambda event: self.run_tests_with_status(
209 event.status,
210 root=test_or_root(event)),
211 Event.Type.REVIEW:
212 lambda event: self.show_review_information(),
213 Event.Type.UPDATE_SYSTEM_INFO:
214 lambda event: self.update_system_info(),
215 Event.Type.UPDATE_FACTORY:
216 lambda event: self.update_factory(),
217 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800218 lambda event: self.stop(root=test_or_root(event, False),
219 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800220 Event.Type.SET_VISIBLE_TEST:
221 lambda event: self.set_visible_test(
222 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800223 }
224
225 self.exceptions = []
226 self.web_socket_manager = None
227
228 def destroy(self):
229 if self.chrome:
230 self.chrome.kill()
231 self.chrome = None
232 if self.ui_process:
233 utils.kill_process_tree(self.ui_process, 'ui')
234 self.ui_process = None
235 if self.web_socket_manager:
236 logging.info('Stopping web sockets')
237 self.web_socket_manager.close()
238 self.web_socket_manager = None
239 if self.state_server_thread:
240 logging.info('Stopping state server')
241 self.state_server.shutdown()
242 self.state_server_thread.join()
243 self.state_server.server_close()
244 self.state_server_thread = None
245 if self.state_instance:
246 self.state_instance.close()
247 if self.event_server_thread:
248 logging.info('Stopping event server')
249 self.event_server.shutdown() # pylint: disable=E1101
250 self.event_server_thread.join()
251 self.event_server.server_close()
252 self.event_server_thread = None
253 if self.log_watcher:
254 if self.log_watcher.IsThreadStarted():
255 self.log_watcher.StopWatchThread()
256 self.log_watcher = None
257 if self.prespawner:
258 logging.info('Stopping prespawner')
259 self.prespawner.stop()
260 self.prespawner = None
261 if self.event_client:
262 logging.info('Closing event client')
263 self.event_client.close()
264 self.event_client = None
265 if self.event_log:
266 self.event_log.Close()
267 self.event_log = None
268 self.check_exceptions()
269 logging.info('Done destroying Goofy')
270
271 def start_state_server(self):
272 self.state_instance, self.state_server = (
273 state.create_server(bind_address='0.0.0.0'))
274 logging.info('Starting state server')
275 self.state_server_thread = threading.Thread(
276 target=self.state_server.serve_forever,
277 name='StateServer')
278 self.state_server_thread.start()
279
280 def start_event_server(self):
281 self.event_server = EventServer()
282 logging.info('Starting factory event server')
283 self.event_server_thread = threading.Thread(
284 target=self.event_server.serve_forever,
285 name='EventServer') # pylint: disable=E1101
286 self.event_server_thread.start()
287
288 self.event_client = EventClient(
289 callback=self.handle_event, event_loop=self.run_queue)
290
291 self.web_socket_manager = WebSocketManager(self.uuid)
292 self.state_server.add_handler("/event",
293 self.web_socket_manager.handle_web_socket)
294
295 def start_ui(self):
296 ui_proc_args = [
297 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
298 self.options.test_list]
299 if self.options.verbose:
300 ui_proc_args.append('-v')
301 logging.info('Starting ui %s', ui_proc_args)
302 self.ui_process = subprocess.Popen(ui_proc_args)
303 logging.info('Waiting for UI to come up...')
304 self.event_client.wait(
305 lambda event: event.type == Event.Type.UI_READY)
306 logging.info('UI has started')
307
308 def set_visible_test(self, test):
309 if self.visible_test == test:
310 return
311
312 if test:
313 test.update_state(visible=True)
314 if self.visible_test:
315 self.visible_test.update_state(visible=False)
316 self.visible_test = test
317
318 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800319 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800320 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800321
Jon Salz0697cbf2012-07-04 15:14:04 +0800322 @param test: The ShutdownStep.
323 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800324 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800325 test_state = test.update_state(increment_shutdown_count=1)
326 logging.info('Detected shutdown (%d of %d)',
327 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800328
Jon Salz0697cbf2012-07-04 15:14:04 +0800329 def log_and_update_state(status, error_msg, **kw):
330 self.event_log.Log('rebooted',
331 status=status, error_msg=error_msg, **kw)
332 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800333
Jon Salz0697cbf2012-07-04 15:14:04 +0800334 if not self.last_shutdown_time:
335 log_and_update_state(status=TestState.FAILED,
336 error_msg='Unable to read shutdown_time')
337 return
Jon Salz258a40c2012-04-19 12:34:01 +0800338
Jon Salz0697cbf2012-07-04 15:14:04 +0800339 now = time.time()
340 logging.info('%.03f s passed since reboot',
341 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800342
Jon Salz0697cbf2012-07-04 15:14:04 +0800343 if self.last_shutdown_time > now:
344 test.update_state(status=TestState.FAILED,
345 error_msg='Time moved backward during reboot')
346 elif (isinstance(test, factory.RebootStep) and
347 self.test_list.options.max_reboot_time_secs and
348 (now - self.last_shutdown_time >
349 self.test_list.options.max_reboot_time_secs)):
350 # A reboot took too long; fail. (We don't check this for
351 # HaltSteps, because the machine could be halted for a
352 # very long time, and even unplugged with battery backup,
353 # thus hosing the clock.)
354 log_and_update_state(
355 status=TestState.FAILED,
356 error_msg=('More than %d s elapsed during reboot '
357 '(%.03f s, from %s to %s)' % (
358 self.test_list.options.max_reboot_time_secs,
359 now - self.last_shutdown_time,
360 utils.TimeString(self.last_shutdown_time),
361 utils.TimeString(now))),
362 duration=(now-self.last_shutdown_time))
363 elif test_state.shutdown_count == test.iterations:
364 # Good!
365 log_and_update_state(status=TestState.PASSED,
366 duration=(now - self.last_shutdown_time),
367 error_msg='')
368 elif test_state.shutdown_count > test.iterations:
369 # Shut down too many times
370 log_and_update_state(status=TestState.FAILED,
371 error_msg='Too many shutdowns')
372 elif utils.are_shift_keys_depressed():
373 logging.info('Shift keys are depressed; cancelling restarts')
374 # Abort shutdown
375 log_and_update_state(
376 status=TestState.FAILED,
377 error_msg='Shutdown aborted with double shift keys')
378 else:
379 def handler():
380 if self._prompt_cancel_shutdown(
381 test, test_state.shutdown_count + 1):
382 log_and_update_state(
383 status=TestState.FAILED,
384 error_msg='Shutdown aborted by operator')
385 return
Jon Salz0405ab52012-03-16 15:26:52 +0800386
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 # Time to shutdown again
388 log_and_update_state(
389 status=TestState.ACTIVE,
390 error_msg='',
391 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800392
Jon Salz0697cbf2012-07-04 15:14:04 +0800393 self.event_log.Log('shutdown', operation='reboot')
394 self.state_instance.set_shared_data('shutdown_time',
395 time.time())
396 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800397
Jon Salz0697cbf2012-07-04 15:14:04 +0800398 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800399
Jon Salz0697cbf2012-07-04 15:14:04 +0800400 def _prompt_cancel_shutdown(self, test, iteration):
401 if self.options.ui != 'chrome':
402 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800403
Jon Salz0697cbf2012-07-04 15:14:04 +0800404 pending_shutdown_data = {
405 'delay_secs': test.delay_secs,
406 'time': time.time() + test.delay_secs,
407 'operation': test.operation,
408 'iteration': iteration,
409 'iterations': test.iterations,
410 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800411
Jon Salz0697cbf2012-07-04 15:14:04 +0800412 # Create a new (threaded) event client since we
413 # don't want to use the event loop for this.
414 with EventClient() as event_client:
415 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
416 **pending_shutdown_data))
417 aborted = event_client.wait(
418 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
419 timeout=test.delay_secs) is not None
420 if aborted:
421 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
422 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800423
Jon Salz0697cbf2012-07-04 15:14:04 +0800424 def init_states(self):
425 '''
426 Initializes all states on startup.
427 '''
428 for test in self.test_list.get_all_tests():
429 # Make sure the state server knows about all the tests,
430 # defaulting to an untested state.
431 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800432
Jon Salz0697cbf2012-07-04 15:14:04 +0800433 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800434
Jon Salz0697cbf2012-07-04 15:14:04 +0800435 # Any 'active' tests should be marked as failed now.
436 for test in self.test_list.walk():
437 test_state = test.get_state()
438 if test_state.status != TestState.ACTIVE:
439 continue
440 if isinstance(test, factory.ShutdownStep):
441 # Shutdown while the test was active - that's good.
442 self.handle_shutdown_complete(test, test_state)
443 else:
444 # Unexpected shutdown. Grab /var/log/messages for context.
445 if var_log_messages is None:
446 try:
447 var_log_messages = (
448 utils.var_log_messages_before_reboot())
449 # Write it to the log, to make it easier to
450 # correlate with /var/log/messages.
451 logging.info(
452 'Unexpected shutdown. '
453 'Tail of /var/log/messages before last reboot:\n'
454 '%s', ('\n'.join(
455 ' ' + x for x in var_log_messages)))
456 except: # pylint: disable=W0702
457 logging.exception('Unable to grok /var/log/messages')
458 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800459
Jon Salz0697cbf2012-07-04 15:14:04 +0800460 error_msg = 'Unexpected shutdown while test was running'
461 self.event_log.Log('end_test',
462 path=test.path,
463 status=TestState.FAILED,
464 invocation=test.get_state().invocation,
465 error_msg=error_msg,
466 var_log_messages='\n'.join(var_log_messages))
467 test.update_state(
468 status=TestState.FAILED,
469 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800470
Jon Salz0697cbf2012-07-04 15:14:04 +0800471 def show_next_active_test(self):
472 '''
473 Rotates to the next visible active test.
474 '''
475 self.reap_completed_tests()
476 active_tests = [
477 t for t in self.test_list.walk()
478 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
479 if not active_tests:
480 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800481
Jon Salz0697cbf2012-07-04 15:14:04 +0800482 try:
483 next_test = active_tests[
484 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
485 except ValueError: # visible_test not present in active_tests
486 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800487
Jon Salz0697cbf2012-07-04 15:14:04 +0800488 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800489
Jon Salz0697cbf2012-07-04 15:14:04 +0800490 def handle_event(self, event):
491 '''
492 Handles an event from the event server.
493 '''
494 handler = self.event_handlers.get(event.type)
495 if handler:
496 handler(event)
497 else:
498 # We don't register handlers for all event types - just ignore
499 # this event.
500 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800501
Jon Salz0697cbf2012-07-04 15:14:04 +0800502 def run_next_test(self):
503 '''
504 Runs the next eligible test (or tests) in self.tests_to_run.
505 '''
506 self.reap_completed_tests()
507 while self.tests_to_run:
508 logging.debug('Tests to run: %s',
509 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800510
Jon Salz0697cbf2012-07-04 15:14:04 +0800511 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800512
Jon Salz0697cbf2012-07-04 15:14:04 +0800513 if test in self.invocations:
514 logging.info('Next test %s is already running', test.path)
515 self.tests_to_run.popleft()
516 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800517
Jon Salz304a75d2012-07-06 11:14:15 +0800518 for i in test.require_run:
519 for j in i.walk():
520 if j.get_state().status == TestState.ACTIVE:
521 logging.info('Waiting for active test %s to complete '
522 'before running %s', j.path, test.path)
523 return
524
Jon Salz0697cbf2012-07-04 15:14:04 +0800525 if self.invocations and not (test.backgroundable and all(
526 [x.backgroundable for x in self.invocations])):
527 logging.debug('Waiting for non-backgroundable tests to '
528 'complete before running %s', test.path)
529 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800530
Jon Salz0697cbf2012-07-04 15:14:04 +0800531 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800532
Jon Salz304a75d2012-07-06 11:14:15 +0800533 untested = set()
534 for i in test.require_run:
535 for j in i.walk():
536 if j == test:
537 # We've hit this test itself; stop checking
538 break
539 if j.get_state().status == TestState.UNTESTED:
540 # Found an untested test; move on to the next
541 # element in require_run.
542 untested.add(j)
543 break
544
545 if untested:
546 untested_paths = ', '.join(sorted([x.path for x in untested]))
547 if self.state_instance.get_shared_data('engineering_mode',
548 optional=True):
549 # In engineering mode, we'll let it go.
550 factory.console.warn('In engineering mode; running '
551 '%s even though required tests '
552 '[%s] have not completed',
553 test.path, untested_paths)
554 else:
555 # Not in engineering mode; mark it failed.
556 error_msg = ('Required tests [%s] have not been run yet'
557 % untested_paths)
558 factory.console.error('Not running %s: %s',
559 test.path, error_msg)
560 test.update_state(status=TestState.FAILED,
561 error_msg=error_msg)
562 continue
563
Jon Salz0697cbf2012-07-04 15:14:04 +0800564 if isinstance(test, factory.ShutdownStep):
565 if os.path.exists(NO_REBOOT_FILE):
566 test.update_state(
567 status=TestState.FAILED, increment_count=1,
568 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800569 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800570 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800571
Jon Salz0697cbf2012-07-04 15:14:04 +0800572 test.update_state(status=TestState.ACTIVE, increment_count=1,
573 error_msg='', shutdown_count=0)
574 if self._prompt_cancel_shutdown(test, 1):
575 self.event_log.Log('reboot_cancelled')
576 test.update_state(
577 status=TestState.FAILED, increment_count=1,
578 error_msg='Shutdown aborted by operator',
579 shutdown_count=0)
580 return
Jon Salz2f757d42012-06-27 17:06:42 +0800581
Jon Salz0697cbf2012-07-04 15:14:04 +0800582 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800583 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800584 'tests_after_shutdown',
585 [t.path for t in self.tests_to_run])
586 # Save shutdown time
587 self.state_instance.set_shared_data('shutdown_time',
588 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800589
Jon Salz0697cbf2012-07-04 15:14:04 +0800590 with self.env.lock:
591 self.event_log.Log('shutdown', operation=test.operation)
592 shutdown_result = self.env.shutdown(test.operation)
593 if shutdown_result:
594 # That's all, folks!
595 self.run_queue.put(None)
596 return
597 else:
598 # Just pass (e.g., in the chroot).
599 test.update_state(status=TestState.PASSED)
600 self.state_instance.set_shared_data(
601 'tests_after_shutdown', None)
602 # Send event with no fields to indicate that there is no
603 # longer a pending shutdown.
604 self.event_client.post_event(Event(
605 Event.Type.PENDING_SHUTDOWN))
606 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800607
Jon Salz0697cbf2012-07-04 15:14:04 +0800608 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
609 self.invocations[test] = invoc
610 if self.visible_test is None and test.has_ui:
611 self.set_visible_test(test)
612 self.check_connection_manager()
613 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800614
Jon Salz0697cbf2012-07-04 15:14:04 +0800615 def check_connection_manager(self):
616 exclusive_tests = [
617 test.path
618 for test in self.invocations
619 if test.is_exclusive(
620 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
621 if exclusive_tests:
622 # Make sure networking is disabled.
623 if self.network_enabled:
624 logging.info('Disabling network, as requested by %s',
625 exclusive_tests)
626 self.connection_manager.DisableNetworking()
627 self.network_enabled = False
628 else:
629 # Make sure networking is enabled.
630 if not self.network_enabled:
631 logging.info('Re-enabling network')
632 self.connection_manager.EnableNetworking()
633 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800634
cychiang21886742012-07-05 15:16:32 +0800635 def check_for_updates(self):
636 '''
637 Schedules an asynchronous check for updates if necessary.
638 '''
639 if not self.test_list.options.update_period_secs:
640 # Not enabled.
641 return
642
643 now = time.time()
644 if self.last_update_check and (
645 now - self.last_update_check <
646 self.test_list.options.update_period_secs):
647 # Not yet time for another check.
648 return
649
650 self.last_update_check = now
651
652 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
653 if reached_shopfloor:
654 new_update_md5sum = md5sum if needs_update else None
655 if system.SystemInfo.update_md5sum != new_update_md5sum:
656 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
657 system.SystemInfo.update_md5sum = new_update_md5sum
658 self.run_queue.put(self.update_system_info)
659
660 updater.CheckForUpdateAsync(
661 handle_check_for_update,
662 self.test_list.options.shopfloor_timeout_secs)
663
Jon Salz0697cbf2012-07-04 15:14:04 +0800664 def run_tests(self, subtrees, untested_only=False):
665 '''
666 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800667
Jon Salz0697cbf2012-07-04 15:14:04 +0800668 The tests are run in order unless one fails (then stops).
669 Backgroundable tests are run simultaneously; when a foreground test is
670 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800671
Jon Salz0697cbf2012-07-04 15:14:04 +0800672 @param subtrees: Node or nodes containing tests to run (may either be
673 a single test or a list). Duplicates will be ignored.
674 '''
675 if type(subtrees) != list:
676 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800677
Jon Salz0697cbf2012-07-04 15:14:04 +0800678 # Nodes we've seen so far, to avoid duplicates.
679 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800680
Jon Salz0697cbf2012-07-04 15:14:04 +0800681 self.tests_to_run = deque()
682 for subtree in subtrees:
683 for test in subtree.walk():
684 if test in seen:
685 continue
686 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800687
Jon Salz0697cbf2012-07-04 15:14:04 +0800688 if not test.is_leaf():
689 continue
690 if (untested_only and
691 test.get_state().status != TestState.UNTESTED):
692 continue
693 self.tests_to_run.append(test)
694 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800695
Jon Salz0697cbf2012-07-04 15:14:04 +0800696 def reap_completed_tests(self):
697 '''
698 Removes completed tests from the set of active tests.
699
700 Also updates the visible test if it was reaped.
701 '''
702 for t, v in dict(self.invocations).iteritems():
703 if v.is_completed():
704 del self.invocations[t]
705
706 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800707 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800708 self.set_visible_test(None)
709 # Make the first running test, if any, the visible test
710 for t in self.test_list.walk():
711 if t in self.invocations:
712 self.set_visible_test(t)
713 break
714
Jon Salz85a39882012-07-05 16:45:04 +0800715 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800716 '''
717 Kills and waits for all active tests.
718
Jon Salz85a39882012-07-05 16:45:04 +0800719 Args:
720 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800721 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800722 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800723 '''
724 self.reap_completed_tests()
725 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800726 if root and not test.has_ancestor(root):
727 continue
728
Jon Salz0697cbf2012-07-04 15:14:04 +0800729 factory.console.info('Killing active test %s...' % test.path)
730 invoc.abort_and_join()
731 factory.console.info('Killed %s' % test.path)
732 del self.invocations[test]
733 if not abort:
734 test.update_state(status=TestState.UNTESTED)
735 self.reap_completed_tests()
736
Jon Salz85a39882012-07-05 16:45:04 +0800737 def stop(self, root=None, fail=False):
738 self.kill_active_tests(fail, root)
739 # Remove any tests in the run queue under the root.
740 self.tests_to_run = deque([x for x in self.tests_to_run
741 if root and not x.has_ancestor(root)])
742 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800743
744 def abort_active_tests(self):
745 self.kill_active_tests(True)
746
747 def main(self):
748 try:
749 self.init()
750 self.event_log.Log('goofy_init',
751 success=True)
752 except:
753 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800754 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800755 self.event_log.Log('goofy_init',
756 success=False,
757 trace=traceback.format_exc())
758 except: # pylint: disable=W0702
759 pass
760 raise
761
762 self.run()
763
764 def update_system_info(self):
765 '''Updates system info.'''
766 system_info = system.SystemInfo()
767 self.state_instance.set_shared_data('system_info', system_info.__dict__)
768 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
769 system_info=system_info.__dict__))
770 logging.info('System info: %r', system_info.__dict__)
771
Jon Salz5c344f62012-07-13 14:31:16 +0800772 def update_factory(self, auto_run_on_restart=False):
773 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800774 self.kill_active_tests(False)
775 self.run_tests([])
776
Jon Salz5c344f62012-07-13 14:31:16 +0800777 def pre_update_hook():
778 if auto_run_on_restart:
779 self.state_instance.set_shared_data('tests_after_shutdown',
780 FORCE_AUTO_RUN)
781 self.state_instance.close()
782
Jon Salz0697cbf2012-07-04 15:14:04 +0800783 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800784 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800785 self.env.shutdown('reboot')
786 except: # pylint: disable=W0702
787 factory.console.exception('Unable to update')
788
789 def init(self, args=None, env=None):
790 '''Initializes Goofy.
791
792 Args:
793 args: A list of command-line arguments. Uses sys.argv if
794 args is None.
795 env: An Environment instance to use (or None to choose
796 FakeChrootEnvironment or DUTEnvironment as appropriate).
797 '''
798 parser = OptionParser()
799 parser.add_option('-v', '--verbose', dest='verbose',
800 action='store_true',
801 help='Enable debug logging')
802 parser.add_option('--print_test_list', dest='print_test_list',
803 metavar='FILE',
804 help='Read and print test list FILE, and exit')
805 parser.add_option('--restart', dest='restart',
806 action='store_true',
807 help='Clear all test state')
808 parser.add_option('--ui', dest='ui', type='choice',
809 choices=['none', 'gtk', 'chrome'],
Jon Salz06f2e852012-07-06 18:37:58 +0800810 default=('chrome' if utils.in_chroot() else 'gtk'),
Jon Salz0697cbf2012-07-04 15:14:04 +0800811 help='UI to use')
812 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
813 type='int', default=1,
814 help=('Factor by which to scale UI '
815 '(Chrome UI only)'))
816 parser.add_option('--test_list', dest='test_list',
817 metavar='FILE',
818 help='Use FILE as test list')
819 (self.options, self.args) = parser.parse_args(args)
820
Jon Salz46b89562012-07-05 11:49:22 +0800821 # Make sure factory directories exist.
822 factory.get_log_root()
823 factory.get_state_root()
824 factory.get_test_data_root()
825
Jon Salz0697cbf2012-07-04 15:14:04 +0800826 global _inited_logging # pylint: disable=W0603
827 if not _inited_logging:
828 factory.init_logging('goofy', verbose=self.options.verbose)
829 _inited_logging = True
830 self.event_log = EventLog('goofy')
831
832 if (not suppress_chroot_warning and
833 factory.in_chroot() and
834 self.options.ui == 'gtk' and
835 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
836 # That's not going to work! Tell the user how to run
837 # this way.
838 logging.warn(GOOFY_IN_CHROOT_WARNING)
839 time.sleep(1)
840
841 if env:
842 self.env = env
843 elif factory.in_chroot():
844 self.env = test_environment.FakeChrootEnvironment()
845 logging.warn(
846 'Using chroot environment: will not actually run autotests')
847 else:
848 self.env = test_environment.DUTEnvironment()
849 self.env.goofy = self
850
851 if self.options.restart:
852 state.clear_state()
853
854 if self.options.print_test_list:
855 print (factory.read_test_list(
856 self.options.print_test_list,
857 test_classes=dict(test_steps.__dict__)).
858 __repr__(recursive=True))
859 return
860
861 if self.options.ui_scale_factor != 1 and utils.in_qemu():
862 logging.warn(
863 'In QEMU; ignoring ui_scale_factor argument')
864 self.options.ui_scale_factor = 1
865
866 logging.info('Started')
867
868 self.start_state_server()
869 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
870 self.state_instance.set_shared_data('ui_scale_factor',
871 self.options.ui_scale_factor)
872 self.last_shutdown_time = (
873 self.state_instance.get_shared_data('shutdown_time', optional=True))
874 self.state_instance.del_shared_data('shutdown_time', optional=True)
875
876 if not self.options.test_list:
877 self.options.test_list = find_test_list()
878 if not self.options.test_list:
879 logging.error('No test list. Aborting.')
880 sys.exit(1)
881 logging.info('Using test list %s', self.options.test_list)
882
883 self.test_list = factory.read_test_list(
884 self.options.test_list,
885 self.state_instance,
886 test_classes=dict(test_steps.__dict__))
887 if not self.state_instance.has_shared_data('ui_lang'):
888 self.state_instance.set_shared_data('ui_lang',
889 self.test_list.options.ui_lang)
890 self.state_instance.set_shared_data(
891 'test_list_options',
892 self.test_list.options.__dict__)
893 self.state_instance.test_list = self.test_list
894
895 self.init_states()
896 self.start_event_server()
897 self.connection_manager = self.env.create_connection_manager(
898 self.test_list.options.wlans)
899 # Note that we create a log watcher even if
900 # sync_event_log_period_secs isn't set (no background
901 # syncing), since we may use it to flush event logs as well.
902 self.log_watcher = EventLogWatcher(
903 self.test_list.options.sync_event_log_period_secs,
904 handle_event_logs_callback=self._handle_event_logs)
905 if self.test_list.options.sync_event_log_period_secs:
906 self.log_watcher.StartWatchThread()
907
908 self.update_system_info()
909
910 os.environ['CROS_FACTORY'] = '1'
911 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
912
913 # Set CROS_UI since some behaviors in ui.py depend on the
914 # particular UI in use. TODO(jsalz): Remove this (and all
915 # places it is used) when the GTK UI is removed.
916 os.environ['CROS_UI'] = self.options.ui
917
918 if self.options.ui == 'chrome':
919 self.env.launch_chrome()
920 logging.info('Waiting for a web socket connection')
921 self.web_socket_manager.wait()
922
923 # Wait for the test widget size to be set; this is done in
924 # an asynchronous RPC so there is a small chance that the
925 # web socket might be opened first.
926 for _ in range(100): # 10 s
927 try:
928 if self.state_instance.get_shared_data('test_widget_size'):
929 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800930 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800931 pass # Retry
932 time.sleep(0.1) # 100 ms
933 else:
934 logging.warn('Never received test_widget_size from UI')
935 elif self.options.ui == 'gtk':
936 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800937
Jon Salz0697cbf2012-07-04 15:14:04 +0800938 for handler in self.on_ui_startup:
939 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800940
Jon Salz0697cbf2012-07-04 15:14:04 +0800941 self.prespawner = Prespawner()
942 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800943
Jon Salz0697cbf2012-07-04 15:14:04 +0800944 def state_change_callback(test, test_state):
945 self.event_client.post_event(
946 Event(Event.Type.STATE_CHANGE,
947 path=test.path, state=test_state))
948 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800949
Jon Salz0697cbf2012-07-04 15:14:04 +0800950 try:
951 tests_after_shutdown = self.state_instance.get_shared_data(
952 'tests_after_shutdown')
953 except KeyError:
954 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800955
Jon Salz5c344f62012-07-13 14:31:16 +0800956 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
957 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +0800958 logging.info('Resuming tests after shutdown: %s',
959 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +0800960 self.tests_to_run.extend(
961 self.test_list.lookup_path(t) for t in tests_after_shutdown)
962 self.run_queue.put(self.run_next_test)
963 else:
Jon Salz5c344f62012-07-13 14:31:16 +0800964 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +0800965 self.run_queue.put(
966 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +0800967 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800968
Jon Salz0697cbf2012-07-04 15:14:04 +0800969 def run(self):
970 '''Runs Goofy.'''
971 # Process events forever.
972 while self.run_once(True):
973 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800974
Jon Salz0697cbf2012-07-04 15:14:04 +0800975 def run_once(self, block=False):
976 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800977
Jon Salz0697cbf2012-07-04 15:14:04 +0800978 Args:
979 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800980
Jon Salz0697cbf2012-07-04 15:14:04 +0800981 Returns:
982 True to keep going or False to shut down.
983 '''
984 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +0800985 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +0800986 # Nothing on the run queue.
987 self._run_queue_idle()
988 if block:
989 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +0800990 try:
991 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
992 except Queue.Empty:
993 # Keep going (calling _run_queue_idle() again at the top of
994 # the loop)
995 continue
Jon Salz0697cbf2012-07-04 15:14:04 +0800996 # ...and grab anything else that showed up at the same
997 # time.
998 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +0800999 else:
1000 break
Jon Salz51528e12012-07-02 18:54:45 +08001001
Jon Salz0697cbf2012-07-04 15:14:04 +08001002 for event in events:
1003 if not event:
1004 # Shutdown request.
1005 self.run_queue.task_done()
1006 return False
Jon Salz51528e12012-07-02 18:54:45 +08001007
Jon Salz0697cbf2012-07-04 15:14:04 +08001008 try:
1009 event()
Jon Salz85a39882012-07-05 16:45:04 +08001010 except: # pylint: disable=W0702
1011 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001012 self.record_exception(traceback.format_exception_only(
1013 *sys.exc_info()[:2]))
1014 # But keep going
1015 finally:
1016 self.run_queue.task_done()
1017 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001018
Jon Salz0697cbf2012-07-04 15:14:04 +08001019 def _run_queue_idle(self):
1020 '''Invoked when the run queue has no events.'''
1021 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001022 self.check_for_updates()
Jon Salz57717ca2012-04-04 16:47:25 +08001023
Jon Salz0697cbf2012-07-04 15:14:04 +08001024 def _handle_event_logs(self, log_name, chunk):
1025 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001026
Jon Salz0697cbf2012-07-04 15:14:04 +08001027 Attempts to upload the event logs to the shopfloor server.
1028 '''
1029 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1030 start_time = time.time()
1031 logging.info('Syncing %s', description)
1032 shopfloor_client = shopfloor.get_instance(
1033 detect=True,
1034 timeout=self.test_list.options.shopfloor_timeout_secs)
1035 shopfloor_client.UploadEvent(log_name, chunk)
1036 logging.info(
1037 'Successfully synced %s in %.03f s',
1038 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001039
Jon Salz0697cbf2012-07-04 15:14:04 +08001040 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1041 root=None):
1042 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001043
Jon Salz0697cbf2012-07-04 15:14:04 +08001044 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001045
Jon Salz0697cbf2012-07-04 15:14:04 +08001046 Args:
1047 starting_at: If provided, only auto-runs tests beginning with
1048 this test.
1049 '''
1050 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001051
Jon Salz0697cbf2012-07-04 15:14:04 +08001052 if starting_at:
1053 # Make sure they passed a test, not a string.
1054 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001055
Jon Salz0697cbf2012-07-04 15:14:04 +08001056 tests_to_reset = []
1057 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001058
Jon Salz0697cbf2012-07-04 15:14:04 +08001059 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001060
Jon Salz0697cbf2012-07-04 15:14:04 +08001061 for test in root.get_top_level_tests():
1062 if starting_at:
1063 if test == starting_at:
1064 # We've found starting_at; do auto-run on all
1065 # subsequent tests.
1066 found_starting_at = True
1067 if not found_starting_at:
1068 # Don't start this guy yet
1069 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001070
Jon Salz0697cbf2012-07-04 15:14:04 +08001071 status = test.get_state().status
1072 if status == TestState.ACTIVE or status in statuses_to_run:
1073 # Reset the test (later; we will need to abort
1074 # all active tests first).
1075 tests_to_reset.append(test)
1076 if status in statuses_to_run:
1077 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001078
Jon Salz0697cbf2012-07-04 15:14:04 +08001079 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001080
Jon Salz0697cbf2012-07-04 15:14:04 +08001081 # Reset all statuses of the tests to run (in case any tests were active;
1082 # we want them to be run again).
1083 for test_to_reset in tests_to_reset:
1084 for test in test_to_reset.walk():
1085 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001086
Jon Salz0697cbf2012-07-04 15:14:04 +08001087 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001088
Jon Salz0697cbf2012-07-04 15:14:04 +08001089 def restart_tests(self, root=None):
1090 '''Restarts all tests.'''
1091 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001092
Jon Salz0697cbf2012-07-04 15:14:04 +08001093 self.abort_active_tests()
1094 for test in root.walk():
1095 test.update_state(status=TestState.UNTESTED)
1096 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001097
Jon Salz0697cbf2012-07-04 15:14:04 +08001098 def auto_run(self, starting_at=None, root=None):
1099 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001100
Jon Salz0697cbf2012-07-04 15:14:04 +08001101 Args:
1102 starting_at: If provide, only auto-runs tests beginning with
1103 this test.
1104 '''
1105 root = root or self.test_list
1106 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1107 starting_at=starting_at,
1108 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001109
Jon Salz0697cbf2012-07-04 15:14:04 +08001110 def re_run_failed(self, root=None):
1111 '''Re-runs failed tests.'''
1112 root = root or self.test_list
1113 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001114
Jon Salz0697cbf2012-07-04 15:14:04 +08001115 def show_review_information(self):
1116 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001117
Jon Salz0697cbf2012-07-04 15:14:04 +08001118 The information screene is rendered by main UI program (ui.py), so in
1119 goofy we only need to kill all active tests, set them as untested, and
1120 clear remaining tests.
1121 '''
1122 self.kill_active_tests(False)
1123 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001124
Jon Salz0697cbf2012-07-04 15:14:04 +08001125 def handle_switch_test(self, event):
1126 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001127
Jon Salz0697cbf2012-07-04 15:14:04 +08001128 @param event: The SWITCH_TEST event.
1129 '''
1130 test = self.test_list.lookup_path(event.path)
1131 if not test:
1132 logging.error('Unknown test %r', event.key)
1133 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001134
Jon Salz0697cbf2012-07-04 15:14:04 +08001135 invoc = self.invocations.get(test)
1136 if invoc and test.backgroundable:
1137 # Already running: just bring to the front if it
1138 # has a UI.
1139 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001140 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001141 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001142
Jon Salz0697cbf2012-07-04 15:14:04 +08001143 self.abort_active_tests()
1144 for t in test.walk():
1145 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001146
Jon Salz0697cbf2012-07-04 15:14:04 +08001147 if self.test_list.options.auto_run_on_keypress:
1148 self.auto_run(starting_at=test)
1149 else:
1150 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001151
Jon Salz0697cbf2012-07-04 15:14:04 +08001152 def wait(self):
1153 '''Waits for all pending invocations.
1154
1155 Useful for testing.
1156 '''
1157 for k, v in self.invocations.iteritems():
1158 logging.info('Waiting for %s to complete...', k)
1159 v.thread.join()
1160
1161 def check_exceptions(self):
1162 '''Raises an error if any exceptions have occurred in
1163 invocation threads.'''
1164 if self.exceptions:
1165 raise RuntimeError('Exception in invocation thread: %r' %
1166 self.exceptions)
1167
1168 def record_exception(self, msg):
1169 '''Records an exception in an invocation thread.
1170
1171 An exception with the given message will be rethrown when
1172 Goofy is destroyed.'''
1173 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001174
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001175
1176if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001177 Goofy().main()