blob: 484d7bec0b1fbb31fead8645223727877089ea69 [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
Jon Salz16d10542012-07-23 12:18:45 +080039from cros.factory.goofy.goofy_rpc import GoofyRPC
Jon Salz83591782012-06-26 11:09:58 +080040from cros.factory.goofy import system
41from cros.factory.goofy import test_environment
Jon Salz8fa8e832012-07-13 19:04:09 +080042from cros.factory.goofy import time_sanitizer
Jon Salz83591782012-06-26 11:09:58 +080043from cros.factory.goofy.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080044
45
Jon Salz2f757d42012-06-27 17:06:42 +080046DEFAULT_TEST_LISTS_DIR = os.path.join(factory.FACTORY_PATH, 'test_lists')
47CUSTOM_DIR = os.path.join(factory.FACTORY_PATH, 'custom')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080048HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
49
Jon Salz8796e362012-05-24 11:39:09 +080050# File that suppresses reboot if present (e.g., for development).
51NO_REBOOT_FILE = '/var/log/factory.noreboot'
52
Jon Salz5c344f62012-07-13 14:31:16 +080053# Value for tests_after_shutdown that forces auto-run (e.g., after
54# a factory update, when the available set of tests might change).
55FORCE_AUTO_RUN = 'force_auto_run'
56
cychiang21886742012-07-05 15:16:32 +080057RUN_QUEUE_TIMEOUT_SECS = 10
58
Jon Salz758e6cc2012-04-03 15:47:07 +080059GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
60You are running Goofy inside the chroot. Autotests are not supported.
61
62To use Goofy in the chroot, first install an Xvnc server:
63
Jon Salz0697cbf2012-07-04 15:14:04 +080064 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080065
66...and then start a VNC X server outside the chroot:
67
Jon Salz0697cbf2012-07-04 15:14:04 +080068 vncserver :10 &
69 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080070
71...and run Goofy as follows:
72
Jon Salz0697cbf2012-07-04 15:14:04 +080073 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080074''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080075suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080076
77def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080078 '''
79 Returns the HWID config tag, or an empty string if none can be found.
80 '''
81 if 'CROS_HWID' in os.environ:
82 return os.environ['CROS_HWID']
83 if os.path.exists(HWID_CFG_PATH):
84 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
85 return hwid_cfg_handle.read().strip()
86 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080087
88
89def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080090 '''
91 Returns the path to the active test list, based on the HWID config tag.
92 '''
93 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080094
Jon Salz0697cbf2012-07-04 15:14:04 +080095 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080096
Jon Salz0697cbf2012-07-04 15:14:04 +080097 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
98 search_files = ['test_list', 'test_list.all']
99 if hwid_cfg:
100 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800101
Jon Salz0697cbf2012-07-04 15:14:04 +0800102 for d in search_dirs:
103 for f in search_files:
104 test_list = os.path.join(d, f)
105 if os.path.exists(test_list):
106 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800107
Jon Salz0697cbf2012-07-04 15:14:04 +0800108 logging.warn('Cannot find test lists named any of %s in any of %s',
109 search_files, search_dirs)
110 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800111
Jon Salz73e0fd02012-04-04 11:46:38 +0800112_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800113
114class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800115 '''
116 The main factory flow.
117
118 Note that all methods in this class must be invoked from the main
119 (event) thread. Other threads, such as callbacks and TestInvocation
120 methods, should instead post events on the run queue.
121
122 TODO: Unit tests. (chrome-os-partner:7409)
123
124 Properties:
125 uuid: A unique UUID for this invocation of Goofy.
126 state_instance: An instance of FactoryState.
127 state_server: The FactoryState XML/RPC server.
128 state_server_thread: A thread running state_server.
129 event_server: The EventServer socket server.
130 event_server_thread: A thread running event_server.
131 event_client: A client to the event server.
132 connection_manager: The connection_manager object.
133 network_enabled: Whether the connection_manager is currently
134 enabling connections.
135 ui_process: The factory ui process object.
136 run_queue: A queue of callbacks to invoke from the main thread.
137 invocations: A map from FactoryTest objects to the corresponding
138 TestInvocations objects representing active tests.
139 tests_to_run: A deque of tests that should be run when the current
140 test(s) complete.
141 options: Command-line options.
142 args: Command-line args.
143 test_list: The test list.
144 event_handlers: Map of Event.Type to the method used to handle that
145 event. If the method has an 'event' argument, the event is passed
146 to the handler.
147 exceptions: Exceptions encountered in invocation threads.
148 '''
149 def __init__(self):
150 self.uuid = str(uuid.uuid4())
151 self.state_instance = None
152 self.state_server = None
153 self.state_server_thread = None
Jon Salz16d10542012-07-23 12:18:45 +0800154 self.goofy_rpc = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800155 self.event_server = None
156 self.event_server_thread = None
157 self.event_client = None
158 self.connection_manager = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800159 self.time_sanitizer = None
160 self.time_synced = False
Jon Salz0697cbf2012-07-04 15:14:04 +0800161 self.log_watcher = None
162 self.network_enabled = True
163 self.event_log = None
164 self.prespawner = None
165 self.ui_process = None
166 self.run_queue = Queue.Queue()
167 self.invocations = {}
168 self.tests_to_run = deque()
169 self.visible_test = None
170 self.chrome = None
171
172 self.options = None
173 self.args = None
174 self.test_list = None
175 self.on_ui_startup = []
176 self.env = None
177 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800178 self.last_update_check = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800179 self.last_sync_time = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800180
Jon Salz85a39882012-07-05 16:45:04 +0800181 def test_or_root(event, parent_or_group=True):
182 '''Returns the test affected by a particular event.
183
184 Args:
185 event: The event containing an optional 'path' attribute.
186 parent_on_group: If True, returns the top-level parent for a test (the
187 root node of the tests that need to be run together if the given test
188 path is to be run).
189 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800190 try:
191 path = event.path
192 except AttributeError:
193 path = None
194
195 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800196 test = self.test_list.lookup_path(path)
197 if parent_or_group:
198 test = test.get_top_level_parent_or_group()
199 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800200 else:
201 return self.test_list
202
203 self.event_handlers = {
204 Event.Type.SWITCH_TEST: self.handle_switch_test,
205 Event.Type.SHOW_NEXT_ACTIVE_TEST:
206 lambda event: self.show_next_active_test(),
207 Event.Type.RESTART_TESTS:
208 lambda event: self.restart_tests(root=test_or_root(event)),
209 Event.Type.AUTO_RUN:
210 lambda event: self.auto_run(root=test_or_root(event)),
211 Event.Type.RE_RUN_FAILED:
212 lambda event: self.re_run_failed(root=test_or_root(event)),
213 Event.Type.RUN_TESTS_WITH_STATUS:
214 lambda event: self.run_tests_with_status(
215 event.status,
216 root=test_or_root(event)),
217 Event.Type.REVIEW:
218 lambda event: self.show_review_information(),
219 Event.Type.UPDATE_SYSTEM_INFO:
220 lambda event: self.update_system_info(),
221 Event.Type.UPDATE_FACTORY:
222 lambda event: self.update_factory(),
223 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800224 lambda event: self.stop(root=test_or_root(event, False),
225 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800226 Event.Type.SET_VISIBLE_TEST:
227 lambda event: self.set_visible_test(
228 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800229 }
230
231 self.exceptions = []
232 self.web_socket_manager = None
233
234 def destroy(self):
235 if self.chrome:
236 self.chrome.kill()
237 self.chrome = None
238 if self.ui_process:
239 utils.kill_process_tree(self.ui_process, 'ui')
240 self.ui_process = None
241 if self.web_socket_manager:
242 logging.info('Stopping web sockets')
243 self.web_socket_manager.close()
244 self.web_socket_manager = None
245 if self.state_server_thread:
246 logging.info('Stopping state server')
247 self.state_server.shutdown()
248 self.state_server_thread.join()
249 self.state_server.server_close()
250 self.state_server_thread = None
251 if self.state_instance:
252 self.state_instance.close()
253 if self.event_server_thread:
254 logging.info('Stopping event server')
255 self.event_server.shutdown() # pylint: disable=E1101
256 self.event_server_thread.join()
257 self.event_server.server_close()
258 self.event_server_thread = None
259 if self.log_watcher:
260 if self.log_watcher.IsThreadStarted():
261 self.log_watcher.StopWatchThread()
262 self.log_watcher = None
263 if self.prespawner:
264 logging.info('Stopping prespawner')
265 self.prespawner.stop()
266 self.prespawner = None
267 if self.event_client:
268 logging.info('Closing event client')
269 self.event_client.close()
270 self.event_client = None
271 if self.event_log:
272 self.event_log.Close()
273 self.event_log = None
274 self.check_exceptions()
275 logging.info('Done destroying Goofy')
276
277 def start_state_server(self):
278 self.state_instance, self.state_server = (
279 state.create_server(bind_address='0.0.0.0'))
Jon Salz16d10542012-07-23 12:18:45 +0800280 self.goofy_rpc = GoofyRPC(self)
281 self.goofy_rpc.RegisterMethods(self.state_instance)
Jon Salz0697cbf2012-07-04 15:14:04 +0800282 logging.info('Starting state server')
283 self.state_server_thread = threading.Thread(
284 target=self.state_server.serve_forever,
285 name='StateServer')
286 self.state_server_thread.start()
287
288 def start_event_server(self):
289 self.event_server = EventServer()
290 logging.info('Starting factory event server')
291 self.event_server_thread = threading.Thread(
292 target=self.event_server.serve_forever,
293 name='EventServer') # pylint: disable=E1101
294 self.event_server_thread.start()
295
296 self.event_client = EventClient(
297 callback=self.handle_event, event_loop=self.run_queue)
298
299 self.web_socket_manager = WebSocketManager(self.uuid)
300 self.state_server.add_handler("/event",
301 self.web_socket_manager.handle_web_socket)
302
303 def start_ui(self):
304 ui_proc_args = [
305 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
306 self.options.test_list]
307 if self.options.verbose:
308 ui_proc_args.append('-v')
309 logging.info('Starting ui %s', ui_proc_args)
310 self.ui_process = subprocess.Popen(ui_proc_args)
311 logging.info('Waiting for UI to come up...')
312 self.event_client.wait(
313 lambda event: event.type == Event.Type.UI_READY)
314 logging.info('UI has started')
315
316 def set_visible_test(self, test):
317 if self.visible_test == test:
318 return
319
320 if test:
321 test.update_state(visible=True)
322 if self.visible_test:
323 self.visible_test.update_state(visible=False)
324 self.visible_test = test
325
326 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800327 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800328 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800329
Jon Salz0697cbf2012-07-04 15:14:04 +0800330 @param test: The ShutdownStep.
331 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800332 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800333 test_state = test.update_state(increment_shutdown_count=1)
334 logging.info('Detected shutdown (%d of %d)',
335 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800336
Jon Salz0697cbf2012-07-04 15:14:04 +0800337 def log_and_update_state(status, error_msg, **kw):
338 self.event_log.Log('rebooted',
339 status=status, error_msg=error_msg, **kw)
340 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800341
Jon Salz0697cbf2012-07-04 15:14:04 +0800342 if not self.last_shutdown_time:
343 log_and_update_state(status=TestState.FAILED,
344 error_msg='Unable to read shutdown_time')
345 return
Jon Salz258a40c2012-04-19 12:34:01 +0800346
Jon Salz0697cbf2012-07-04 15:14:04 +0800347 now = time.time()
348 logging.info('%.03f s passed since reboot',
349 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800350
Jon Salz0697cbf2012-07-04 15:14:04 +0800351 if self.last_shutdown_time > now:
352 test.update_state(status=TestState.FAILED,
353 error_msg='Time moved backward during reboot')
354 elif (isinstance(test, factory.RebootStep) and
355 self.test_list.options.max_reboot_time_secs and
356 (now - self.last_shutdown_time >
357 self.test_list.options.max_reboot_time_secs)):
358 # A reboot took too long; fail. (We don't check this for
359 # HaltSteps, because the machine could be halted for a
360 # very long time, and even unplugged with battery backup,
361 # thus hosing the clock.)
362 log_and_update_state(
363 status=TestState.FAILED,
364 error_msg=('More than %d s elapsed during reboot '
365 '(%.03f s, from %s to %s)' % (
366 self.test_list.options.max_reboot_time_secs,
367 now - self.last_shutdown_time,
368 utils.TimeString(self.last_shutdown_time),
369 utils.TimeString(now))),
370 duration=(now-self.last_shutdown_time))
371 elif test_state.shutdown_count == test.iterations:
372 # Good!
373 log_and_update_state(status=TestState.PASSED,
374 duration=(now - self.last_shutdown_time),
375 error_msg='')
376 elif test_state.shutdown_count > test.iterations:
377 # Shut down too many times
378 log_and_update_state(status=TestState.FAILED,
379 error_msg='Too many shutdowns')
380 elif utils.are_shift_keys_depressed():
381 logging.info('Shift keys are depressed; cancelling restarts')
382 # Abort shutdown
383 log_and_update_state(
384 status=TestState.FAILED,
385 error_msg='Shutdown aborted with double shift keys')
Jon Salza6711d72012-07-18 14:33:03 +0800386 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 else:
388 def handler():
389 if self._prompt_cancel_shutdown(
390 test, test_state.shutdown_count + 1):
Jon Salza6711d72012-07-18 14:33:03 +0800391 factory.console.info('Shutdown aborted by operator')
Jon Salz0697cbf2012-07-04 15:14:04 +0800392 log_and_update_state(
393 status=TestState.FAILED,
394 error_msg='Shutdown aborted by operator')
Jon Salza6711d72012-07-18 14:33:03 +0800395 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800396 return
Jon Salz0405ab52012-03-16 15:26:52 +0800397
Jon Salz0697cbf2012-07-04 15:14:04 +0800398 # Time to shutdown again
399 log_and_update_state(
400 status=TestState.ACTIVE,
401 error_msg='',
402 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800403
Jon Salz0697cbf2012-07-04 15:14:04 +0800404 self.event_log.Log('shutdown', operation='reboot')
405 self.state_instance.set_shared_data('shutdown_time',
406 time.time())
407 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800408
Jon Salz0697cbf2012-07-04 15:14:04 +0800409 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800410
Jon Salz0697cbf2012-07-04 15:14:04 +0800411 def _prompt_cancel_shutdown(self, test, iteration):
412 if self.options.ui != 'chrome':
413 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800414
Jon Salz0697cbf2012-07-04 15:14:04 +0800415 pending_shutdown_data = {
416 'delay_secs': test.delay_secs,
417 'time': time.time() + test.delay_secs,
418 'operation': test.operation,
419 'iteration': iteration,
420 'iterations': test.iterations,
421 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800422
Jon Salz0697cbf2012-07-04 15:14:04 +0800423 # Create a new (threaded) event client since we
424 # don't want to use the event loop for this.
425 with EventClient() as event_client:
426 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
427 **pending_shutdown_data))
428 aborted = event_client.wait(
429 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
430 timeout=test.delay_secs) is not None
431 if aborted:
432 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
433 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800434
Jon Salz0697cbf2012-07-04 15:14:04 +0800435 def init_states(self):
436 '''
437 Initializes all states on startup.
438 '''
439 for test in self.test_list.get_all_tests():
440 # Make sure the state server knows about all the tests,
441 # defaulting to an untested state.
442 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800443
Jon Salz0697cbf2012-07-04 15:14:04 +0800444 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800445
Jon Salz0697cbf2012-07-04 15:14:04 +0800446 # Any 'active' tests should be marked as failed now.
447 for test in self.test_list.walk():
Jon Salza6711d72012-07-18 14:33:03 +0800448 if not test.is_leaf():
449 # Don't bother with parents; they will be updated when their
450 # children are updated.
451 continue
452
Jon Salz0697cbf2012-07-04 15:14:04 +0800453 test_state = test.get_state()
454 if test_state.status != TestState.ACTIVE:
455 continue
456 if isinstance(test, factory.ShutdownStep):
457 # Shutdown while the test was active - that's good.
458 self.handle_shutdown_complete(test, test_state)
459 else:
460 # Unexpected shutdown. Grab /var/log/messages for context.
461 if var_log_messages is None:
462 try:
463 var_log_messages = (
464 utils.var_log_messages_before_reboot())
465 # Write it to the log, to make it easier to
466 # correlate with /var/log/messages.
467 logging.info(
468 'Unexpected shutdown. '
469 'Tail of /var/log/messages before last reboot:\n'
470 '%s', ('\n'.join(
471 ' ' + x for x in var_log_messages)))
472 except: # pylint: disable=W0702
473 logging.exception('Unable to grok /var/log/messages')
474 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800475
Jon Salz0697cbf2012-07-04 15:14:04 +0800476 error_msg = 'Unexpected shutdown while test was running'
477 self.event_log.Log('end_test',
478 path=test.path,
479 status=TestState.FAILED,
480 invocation=test.get_state().invocation,
481 error_msg=error_msg,
482 var_log_messages='\n'.join(var_log_messages))
483 test.update_state(
484 status=TestState.FAILED,
485 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800486
Jon Salz69806bb2012-07-20 18:05:02 +0800487 factory.console.info('Unexpected shutdown while test %s '
488 'running; cancelling any pending tests',
489 test.path)
490 self.state_instance.set_shared_data('tests_after_shutdown', [])
491
Jon Salz0697cbf2012-07-04 15:14:04 +0800492 def show_next_active_test(self):
493 '''
494 Rotates to the next visible active test.
495 '''
496 self.reap_completed_tests()
497 active_tests = [
498 t for t in self.test_list.walk()
499 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
500 if not active_tests:
501 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800502
Jon Salz0697cbf2012-07-04 15:14:04 +0800503 try:
504 next_test = active_tests[
505 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
506 except ValueError: # visible_test not present in active_tests
507 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800508
Jon Salz0697cbf2012-07-04 15:14:04 +0800509 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800510
Jon Salz0697cbf2012-07-04 15:14:04 +0800511 def handle_event(self, event):
512 '''
513 Handles an event from the event server.
514 '''
515 handler = self.event_handlers.get(event.type)
516 if handler:
517 handler(event)
518 else:
519 # We don't register handlers for all event types - just ignore
520 # this event.
521 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800522
Jon Salz0697cbf2012-07-04 15:14:04 +0800523 def run_next_test(self):
524 '''
525 Runs the next eligible test (or tests) in self.tests_to_run.
526 '''
527 self.reap_completed_tests()
528 while self.tests_to_run:
529 logging.debug('Tests to run: %s',
530 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800531
Jon Salz0697cbf2012-07-04 15:14:04 +0800532 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800533
Jon Salz0697cbf2012-07-04 15:14:04 +0800534 if test in self.invocations:
535 logging.info('Next test %s is already running', test.path)
536 self.tests_to_run.popleft()
537 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800538
Jon Salz304a75d2012-07-06 11:14:15 +0800539 for i in test.require_run:
540 for j in i.walk():
541 if j.get_state().status == TestState.ACTIVE:
542 logging.info('Waiting for active test %s to complete '
543 'before running %s', j.path, test.path)
544 return
545
Jon Salz0697cbf2012-07-04 15:14:04 +0800546 if self.invocations and not (test.backgroundable and all(
547 [x.backgroundable for x in self.invocations])):
548 logging.debug('Waiting for non-backgroundable tests to '
549 'complete before running %s', test.path)
550 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800551
Jon Salz0697cbf2012-07-04 15:14:04 +0800552 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800553
Jon Salz304a75d2012-07-06 11:14:15 +0800554 untested = set()
555 for i in test.require_run:
556 for j in i.walk():
557 if j == test:
558 # We've hit this test itself; stop checking
559 break
560 if j.get_state().status == TestState.UNTESTED:
561 # Found an untested test; move on to the next
562 # element in require_run.
563 untested.add(j)
564 break
565
566 if untested:
567 untested_paths = ', '.join(sorted([x.path for x in untested]))
568 if self.state_instance.get_shared_data('engineering_mode',
569 optional=True):
570 # In engineering mode, we'll let it go.
571 factory.console.warn('In engineering mode; running '
572 '%s even though required tests '
573 '[%s] have not completed',
574 test.path, untested_paths)
575 else:
576 # Not in engineering mode; mark it failed.
577 error_msg = ('Required tests [%s] have not been run yet'
578 % untested_paths)
579 factory.console.error('Not running %s: %s',
580 test.path, error_msg)
581 test.update_state(status=TestState.FAILED,
582 error_msg=error_msg)
583 continue
584
Jon Salz0697cbf2012-07-04 15:14:04 +0800585 if isinstance(test, factory.ShutdownStep):
586 if os.path.exists(NO_REBOOT_FILE):
587 test.update_state(
588 status=TestState.FAILED, increment_count=1,
589 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800590 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800591 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800592
Jon Salz0697cbf2012-07-04 15:14:04 +0800593 test.update_state(status=TestState.ACTIVE, increment_count=1,
594 error_msg='', shutdown_count=0)
595 if self._prompt_cancel_shutdown(test, 1):
596 self.event_log.Log('reboot_cancelled')
597 test.update_state(
598 status=TestState.FAILED, increment_count=1,
599 error_msg='Shutdown aborted by operator',
600 shutdown_count=0)
601 return
Jon Salz2f757d42012-06-27 17:06:42 +0800602
Jon Salz0697cbf2012-07-04 15:14:04 +0800603 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800604 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800605 'tests_after_shutdown',
606 [t.path for t in self.tests_to_run])
607 # Save shutdown time
608 self.state_instance.set_shared_data('shutdown_time',
609 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800610
Jon Salz0697cbf2012-07-04 15:14:04 +0800611 with self.env.lock:
612 self.event_log.Log('shutdown', operation=test.operation)
613 shutdown_result = self.env.shutdown(test.operation)
614 if shutdown_result:
615 # That's all, folks!
616 self.run_queue.put(None)
617 return
618 else:
619 # Just pass (e.g., in the chroot).
620 test.update_state(status=TestState.PASSED)
621 self.state_instance.set_shared_data(
622 'tests_after_shutdown', None)
623 # Send event with no fields to indicate that there is no
624 # longer a pending shutdown.
625 self.event_client.post_event(Event(
626 Event.Type.PENDING_SHUTDOWN))
627 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800628
Jon Salz1acc8742012-07-17 17:45:55 +0800629 self._run_test(test, test.iterations)
630
631 def _run_test(self, test, iterations_left=None):
632 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
633 new_state = test.update_state(
634 status=TestState.ACTIVE, increment_count=1, error_msg='',
635 invocation=invoc.uuid, iterations_left=iterations_left)
636 invoc.count = new_state.count
637
638 self.invocations[test] = invoc
639 if self.visible_test is None and test.has_ui:
640 self.set_visible_test(test)
641 self.check_connection_manager()
642 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800643
Jon Salz0697cbf2012-07-04 15:14:04 +0800644 def check_connection_manager(self):
645 exclusive_tests = [
646 test.path
647 for test in self.invocations
648 if test.is_exclusive(
649 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
650 if exclusive_tests:
651 # Make sure networking is disabled.
652 if self.network_enabled:
653 logging.info('Disabling network, as requested by %s',
654 exclusive_tests)
655 self.connection_manager.DisableNetworking()
656 self.network_enabled = False
657 else:
658 # Make sure networking is enabled.
659 if not self.network_enabled:
660 logging.info('Re-enabling network')
661 self.connection_manager.EnableNetworking()
662 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800663
cychiang21886742012-07-05 15:16:32 +0800664 def check_for_updates(self):
665 '''
666 Schedules an asynchronous check for updates if necessary.
667 '''
668 if not self.test_list.options.update_period_secs:
669 # Not enabled.
670 return
671
672 now = time.time()
673 if self.last_update_check and (
674 now - self.last_update_check <
675 self.test_list.options.update_period_secs):
676 # Not yet time for another check.
677 return
678
679 self.last_update_check = now
680
681 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
682 if reached_shopfloor:
683 new_update_md5sum = md5sum if needs_update else None
684 if system.SystemInfo.update_md5sum != new_update_md5sum:
685 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
686 system.SystemInfo.update_md5sum = new_update_md5sum
687 self.run_queue.put(self.update_system_info)
688
689 updater.CheckForUpdateAsync(
690 handle_check_for_update,
691 self.test_list.options.shopfloor_timeout_secs)
692
Jon Salza6711d72012-07-18 14:33:03 +0800693 def cancel_pending_tests(self):
694 '''Cancels any tests in the run queue.'''
695 self.run_tests([])
696
Jon Salz0697cbf2012-07-04 15:14:04 +0800697 def run_tests(self, subtrees, untested_only=False):
698 '''
699 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800700
Jon Salz0697cbf2012-07-04 15:14:04 +0800701 The tests are run in order unless one fails (then stops).
702 Backgroundable tests are run simultaneously; when a foreground test is
703 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800704
Jon Salz0697cbf2012-07-04 15:14:04 +0800705 @param subtrees: Node or nodes containing tests to run (may either be
706 a single test or a list). Duplicates will be ignored.
707 '''
708 if type(subtrees) != list:
709 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800710
Jon Salz0697cbf2012-07-04 15:14:04 +0800711 # Nodes we've seen so far, to avoid duplicates.
712 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800713
Jon Salz0697cbf2012-07-04 15:14:04 +0800714 self.tests_to_run = deque()
715 for subtree in subtrees:
716 for test in subtree.walk():
717 if test in seen:
718 continue
719 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800720
Jon Salz0697cbf2012-07-04 15:14:04 +0800721 if not test.is_leaf():
722 continue
723 if (untested_only and
724 test.get_state().status != TestState.UNTESTED):
725 continue
726 self.tests_to_run.append(test)
727 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800728
Jon Salz0697cbf2012-07-04 15:14:04 +0800729 def reap_completed_tests(self):
730 '''
731 Removes completed tests from the set of active tests.
732
733 Also updates the visible test if it was reaped.
734 '''
735 for t, v in dict(self.invocations).iteritems():
736 if v.is_completed():
Jon Salz1acc8742012-07-17 17:45:55 +0800737 new_state = t.update_state(**v.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800738 del self.invocations[t]
739
Jon Salz1acc8742012-07-17 17:45:55 +0800740 if new_state.iterations_left and new_state.status == TestState.PASSED:
741 # Play it again, Sam!
742 self._run_test(t)
743
Jon Salz0697cbf2012-07-04 15:14:04 +0800744 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800745 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800746 self.set_visible_test(None)
747 # Make the first running test, if any, the visible test
748 for t in self.test_list.walk():
749 if t in self.invocations:
750 self.set_visible_test(t)
751 break
752
Jon Salz85a39882012-07-05 16:45:04 +0800753 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800754 '''
755 Kills and waits for all active tests.
756
Jon Salz85a39882012-07-05 16:45:04 +0800757 Args:
758 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800759 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800760 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800761 '''
762 self.reap_completed_tests()
763 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800764 if root and not test.has_ancestor(root):
765 continue
766
Jon Salz0697cbf2012-07-04 15:14:04 +0800767 factory.console.info('Killing active test %s...' % test.path)
768 invoc.abort_and_join()
769 factory.console.info('Killed %s' % test.path)
Jon Salz1acc8742012-07-17 17:45:55 +0800770 test.update_state(**invoc.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800771 del self.invocations[test]
Jon Salz1acc8742012-07-17 17:45:55 +0800772
Jon Salz0697cbf2012-07-04 15:14:04 +0800773 if not abort:
774 test.update_state(status=TestState.UNTESTED)
775 self.reap_completed_tests()
776
Jon Salz85a39882012-07-05 16:45:04 +0800777 def stop(self, root=None, fail=False):
778 self.kill_active_tests(fail, root)
779 # Remove any tests in the run queue under the root.
780 self.tests_to_run = deque([x for x in self.tests_to_run
781 if root and not x.has_ancestor(root)])
782 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800783
784 def abort_active_tests(self):
785 self.kill_active_tests(True)
786
787 def main(self):
788 try:
789 self.init()
790 self.event_log.Log('goofy_init',
791 success=True)
792 except:
793 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800794 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800795 self.event_log.Log('goofy_init',
796 success=False,
797 trace=traceback.format_exc())
798 except: # pylint: disable=W0702
799 pass
800 raise
801
802 self.run()
803
804 def update_system_info(self):
805 '''Updates system info.'''
806 system_info = system.SystemInfo()
807 self.state_instance.set_shared_data('system_info', system_info.__dict__)
808 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
809 system_info=system_info.__dict__))
810 logging.info('System info: %r', system_info.__dict__)
811
Jon Salz5c344f62012-07-13 14:31:16 +0800812 def update_factory(self, auto_run_on_restart=False):
813 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800814 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +0800815 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800816
Jon Salz5c344f62012-07-13 14:31:16 +0800817 def pre_update_hook():
818 if auto_run_on_restart:
819 self.state_instance.set_shared_data('tests_after_shutdown',
820 FORCE_AUTO_RUN)
821 self.state_instance.close()
822
Jon Salz0697cbf2012-07-04 15:14:04 +0800823 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800824 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800825 self.env.shutdown('reboot')
826 except: # pylint: disable=W0702
827 factory.console.exception('Unable to update')
828
829 def init(self, args=None, env=None):
830 '''Initializes Goofy.
831
832 Args:
833 args: A list of command-line arguments. Uses sys.argv if
834 args is None.
835 env: An Environment instance to use (or None to choose
836 FakeChrootEnvironment or DUTEnvironment as appropriate).
837 '''
838 parser = OptionParser()
839 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +0800840 action='store_true',
841 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +0800842 parser.add_option('--print_test_list', dest='print_test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800843 metavar='FILE',
844 help='Read and print test list FILE, and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +0800845 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +0800846 action='store_true',
847 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +0800848 parser.add_option('--ui', dest='ui', type='choice',
Jon Salz8fa8e832012-07-13 19:04:09 +0800849 choices=['none', 'gtk', 'chrome'],
850 default=('chrome' if utils.in_chroot() else 'gtk'),
851 help='UI to use')
Jon Salz0697cbf2012-07-04 15:14:04 +0800852 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
Jon Salz8fa8e832012-07-13 19:04:09 +0800853 type='int', default=1,
854 help=('Factor by which to scale UI '
855 '(Chrome UI only)'))
Jon Salz0697cbf2012-07-04 15:14:04 +0800856 parser.add_option('--test_list', dest='test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800857 metavar='FILE',
858 help='Use FILE as test list')
Jon Salz0697cbf2012-07-04 15:14:04 +0800859 (self.options, self.args) = parser.parse_args(args)
860
Jon Salz46b89562012-07-05 11:49:22 +0800861 # Make sure factory directories exist.
862 factory.get_log_root()
863 factory.get_state_root()
864 factory.get_test_data_root()
865
Jon Salz0697cbf2012-07-04 15:14:04 +0800866 global _inited_logging # pylint: disable=W0603
867 if not _inited_logging:
868 factory.init_logging('goofy', verbose=self.options.verbose)
869 _inited_logging = True
Jon Salz8fa8e832012-07-13 19:04:09 +0800870
Jon Salz0697cbf2012-07-04 15:14:04 +0800871 self.event_log = EventLog('goofy')
872
873 if (not suppress_chroot_warning and
874 factory.in_chroot() and
875 self.options.ui == 'gtk' and
876 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
877 # That's not going to work! Tell the user how to run
878 # this way.
879 logging.warn(GOOFY_IN_CHROOT_WARNING)
880 time.sleep(1)
881
882 if env:
883 self.env = env
884 elif factory.in_chroot():
885 self.env = test_environment.FakeChrootEnvironment()
886 logging.warn(
887 'Using chroot environment: will not actually run autotests')
888 else:
889 self.env = test_environment.DUTEnvironment()
890 self.env.goofy = self
891
892 if self.options.restart:
893 state.clear_state()
894
895 if self.options.print_test_list:
896 print (factory.read_test_list(
897 self.options.print_test_list,
898 test_classes=dict(test_steps.__dict__)).
899 __repr__(recursive=True))
900 return
901
902 if self.options.ui_scale_factor != 1 and utils.in_qemu():
903 logging.warn(
904 'In QEMU; ignoring ui_scale_factor argument')
905 self.options.ui_scale_factor = 1
906
907 logging.info('Started')
908
909 self.start_state_server()
910 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
911 self.state_instance.set_shared_data('ui_scale_factor',
912 self.options.ui_scale_factor)
913 self.last_shutdown_time = (
914 self.state_instance.get_shared_data('shutdown_time', optional=True))
915 self.state_instance.del_shared_data('shutdown_time', optional=True)
916
917 if not self.options.test_list:
918 self.options.test_list = find_test_list()
919 if not self.options.test_list:
920 logging.error('No test list. Aborting.')
921 sys.exit(1)
922 logging.info('Using test list %s', self.options.test_list)
923
924 self.test_list = factory.read_test_list(
925 self.options.test_list,
926 self.state_instance,
927 test_classes=dict(test_steps.__dict__))
928 if not self.state_instance.has_shared_data('ui_lang'):
929 self.state_instance.set_shared_data('ui_lang',
930 self.test_list.options.ui_lang)
931 self.state_instance.set_shared_data(
932 'test_list_options',
933 self.test_list.options.__dict__)
934 self.state_instance.test_list = self.test_list
935
Jon Salz8fa8e832012-07-13 19:04:09 +0800936 if self.test_list.options.time_sanitizer:
937 self.time_sanitizer = time_sanitizer.TimeSanitizer(
938 base_time=time_sanitizer.GetBaseTimeFromFile(
939 # lsb-factory is written by the factory install shim during
940 # installation, so it should have a good time obtained from
941 # the mini-Omaha server.
942 '/usr/local/etc/lsb-factory'))
943 self.time_sanitizer.RunOnce()
944
Jon Salz0697cbf2012-07-04 15:14:04 +0800945 self.init_states()
946 self.start_event_server()
947 self.connection_manager = self.env.create_connection_manager(
948 self.test_list.options.wlans)
949 # Note that we create a log watcher even if
950 # sync_event_log_period_secs isn't set (no background
951 # syncing), since we may use it to flush event logs as well.
952 self.log_watcher = EventLogWatcher(
953 self.test_list.options.sync_event_log_period_secs,
Jon Salz16d10542012-07-23 12:18:45 +0800954 handle_event_logs_callback=self.handle_event_logs)
Jon Salz0697cbf2012-07-04 15:14:04 +0800955 if self.test_list.options.sync_event_log_period_secs:
956 self.log_watcher.StartWatchThread()
957
958 self.update_system_info()
959
960 os.environ['CROS_FACTORY'] = '1'
961 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
962
963 # Set CROS_UI since some behaviors in ui.py depend on the
964 # particular UI in use. TODO(jsalz): Remove this (and all
965 # places it is used) when the GTK UI is removed.
966 os.environ['CROS_UI'] = self.options.ui
967
968 if self.options.ui == 'chrome':
969 self.env.launch_chrome()
970 logging.info('Waiting for a web socket connection')
971 self.web_socket_manager.wait()
972
973 # Wait for the test widget size to be set; this is done in
974 # an asynchronous RPC so there is a small chance that the
975 # web socket might be opened first.
976 for _ in range(100): # 10 s
977 try:
978 if self.state_instance.get_shared_data('test_widget_size'):
979 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800980 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800981 pass # Retry
982 time.sleep(0.1) # 100 ms
983 else:
984 logging.warn('Never received test_widget_size from UI')
985 elif self.options.ui == 'gtk':
986 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800987
Jon Salz0697cbf2012-07-04 15:14:04 +0800988 def state_change_callback(test, test_state):
989 self.event_client.post_event(
990 Event(Event.Type.STATE_CHANGE,
991 path=test.path, state=test_state))
992 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800993
Jon Salza6711d72012-07-18 14:33:03 +0800994 for handler in self.on_ui_startup:
995 handler()
996
997 self.prespawner = Prespawner()
998 self.prespawner.start()
999
Jon Salz0697cbf2012-07-04 15:14:04 +08001000 try:
1001 tests_after_shutdown = self.state_instance.get_shared_data(
1002 'tests_after_shutdown')
1003 except KeyError:
1004 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +08001005
Jon Salz5c344f62012-07-13 14:31:16 +08001006 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
1007 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +08001008 logging.info('Resuming tests after shutdown: %s',
1009 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +08001010 self.tests_to_run.extend(
1011 self.test_list.lookup_path(t) for t in tests_after_shutdown)
1012 self.run_queue.put(self.run_next_test)
1013 else:
Jon Salz5c344f62012-07-13 14:31:16 +08001014 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +08001015 self.run_queue.put(
1016 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +08001017 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001018
Jon Salz0697cbf2012-07-04 15:14:04 +08001019 def run(self):
1020 '''Runs Goofy.'''
1021 # Process events forever.
1022 while self.run_once(True):
1023 pass
Jon Salz73e0fd02012-04-04 11:46:38 +08001024
Jon Salz0697cbf2012-07-04 15:14:04 +08001025 def run_once(self, block=False):
1026 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001027
Jon Salz0697cbf2012-07-04 15:14:04 +08001028 Args:
1029 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +08001030
Jon Salz0697cbf2012-07-04 15:14:04 +08001031 Returns:
1032 True to keep going or False to shut down.
1033 '''
1034 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +08001035 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +08001036 # Nothing on the run queue.
1037 self._run_queue_idle()
1038 if block:
1039 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +08001040 try:
1041 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
1042 except Queue.Empty:
1043 # Keep going (calling _run_queue_idle() again at the top of
1044 # the loop)
1045 continue
Jon Salz0697cbf2012-07-04 15:14:04 +08001046 # ...and grab anything else that showed up at the same
1047 # time.
1048 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +08001049 else:
1050 break
Jon Salz51528e12012-07-02 18:54:45 +08001051
Jon Salz0697cbf2012-07-04 15:14:04 +08001052 for event in events:
1053 if not event:
1054 # Shutdown request.
1055 self.run_queue.task_done()
1056 return False
Jon Salz51528e12012-07-02 18:54:45 +08001057
Jon Salz0697cbf2012-07-04 15:14:04 +08001058 try:
1059 event()
Jon Salz85a39882012-07-05 16:45:04 +08001060 except: # pylint: disable=W0702
1061 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001062 self.record_exception(traceback.format_exception_only(
1063 *sys.exc_info()[:2]))
1064 # But keep going
1065 finally:
1066 self.run_queue.task_done()
1067 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001068
Jon Salz8fa8e832012-07-13 19:04:09 +08001069 def sync_time_in_background(self):
1070 '''Attempts to sync time with the shopfloor server.'''
1071 if ((not self.test_list.options.sync_time_period_secs) or
1072 (not self.time_sanitizer) or
1073 self.time_synced or
1074 factory.in_chroot()):
1075 # Not enabled or already succeeded.
1076 return
1077
1078 now = time.time()
1079 if self.last_sync_time and (
1080 now - self.last_sync_time <
1081 self.test_list.options.sync_time_period_secs):
1082 # Not yet time for another check.
1083 return
1084 self.last_sync_time = now
1085
1086 def target():
1087 try:
1088 self.time_sanitizer.SyncWithShopfloor()
1089 self.time_synced = True
1090 except: # pylint: disable=W0702
1091 # Oh well. Log an error (but no trace)
1092 logging.info(
1093 'Unable to get time from shopfloor server: %s',
1094 utils.FormatExceptionOnly())
1095
1096 thread = threading.Thread(target=target)
1097 thread.daemon = True
1098 thread.start()
1099
Jon Salz0697cbf2012-07-04 15:14:04 +08001100 def _run_queue_idle(self):
1101 '''Invoked when the run queue has no events.'''
1102 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001103 self.check_for_updates()
Jon Salz8fa8e832012-07-13 19:04:09 +08001104 self.sync_time_in_background()
Jon Salz57717ca2012-04-04 16:47:25 +08001105
Jon Salz16d10542012-07-23 12:18:45 +08001106 def handle_event_logs(self, log_name, chunk):
Jon Salz0697cbf2012-07-04 15:14:04 +08001107 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001108
Jon Salz0697cbf2012-07-04 15:14:04 +08001109 Attempts to upload the event logs to the shopfloor server.
1110 '''
1111 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1112 start_time = time.time()
1113 logging.info('Syncing %s', description)
1114 shopfloor_client = shopfloor.get_instance(
1115 detect=True,
1116 timeout=self.test_list.options.shopfloor_timeout_secs)
1117 shopfloor_client.UploadEvent(log_name, chunk)
1118 logging.info(
1119 'Successfully synced %s in %.03f s',
1120 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001121
Jon Salz0697cbf2012-07-04 15:14:04 +08001122 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1123 root=None):
1124 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001125
Jon Salz0697cbf2012-07-04 15:14:04 +08001126 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001127
Jon Salz0697cbf2012-07-04 15:14:04 +08001128 Args:
1129 starting_at: If provided, only auto-runs tests beginning with
1130 this test.
1131 '''
1132 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001133
Jon Salz0697cbf2012-07-04 15:14:04 +08001134 if starting_at:
1135 # Make sure they passed a test, not a string.
1136 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001137
Jon Salz0697cbf2012-07-04 15:14:04 +08001138 tests_to_reset = []
1139 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001140
Jon Salz0697cbf2012-07-04 15:14:04 +08001141 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001142
Jon Salz0697cbf2012-07-04 15:14:04 +08001143 for test in root.get_top_level_tests():
1144 if starting_at:
1145 if test == starting_at:
1146 # We've found starting_at; do auto-run on all
1147 # subsequent tests.
1148 found_starting_at = True
1149 if not found_starting_at:
1150 # Don't start this guy yet
1151 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001152
Jon Salz0697cbf2012-07-04 15:14:04 +08001153 status = test.get_state().status
1154 if status == TestState.ACTIVE or status in statuses_to_run:
1155 # Reset the test (later; we will need to abort
1156 # all active tests first).
1157 tests_to_reset.append(test)
1158 if status in statuses_to_run:
1159 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001160
Jon Salz0697cbf2012-07-04 15:14:04 +08001161 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001162
Jon Salz0697cbf2012-07-04 15:14:04 +08001163 # Reset all statuses of the tests to run (in case any tests were active;
1164 # we want them to be run again).
1165 for test_to_reset in tests_to_reset:
1166 for test in test_to_reset.walk():
1167 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001168
Jon Salz0697cbf2012-07-04 15:14:04 +08001169 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001170
Jon Salz0697cbf2012-07-04 15:14:04 +08001171 def restart_tests(self, root=None):
1172 '''Restarts all tests.'''
1173 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001174
Jon Salz0697cbf2012-07-04 15:14:04 +08001175 self.abort_active_tests()
1176 for test in root.walk():
1177 test.update_state(status=TestState.UNTESTED)
1178 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001179
Jon Salz0697cbf2012-07-04 15:14:04 +08001180 def auto_run(self, starting_at=None, root=None):
1181 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001182
Jon Salz0697cbf2012-07-04 15:14:04 +08001183 Args:
1184 starting_at: If provide, only auto-runs tests beginning with
1185 this test.
1186 '''
1187 root = root or self.test_list
1188 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1189 starting_at=starting_at,
1190 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001191
Jon Salz0697cbf2012-07-04 15:14:04 +08001192 def re_run_failed(self, root=None):
1193 '''Re-runs failed tests.'''
1194 root = root or self.test_list
1195 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001196
Jon Salz0697cbf2012-07-04 15:14:04 +08001197 def show_review_information(self):
1198 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001199
Jon Salz0697cbf2012-07-04 15:14:04 +08001200 The information screene is rendered by main UI program (ui.py), so in
1201 goofy we only need to kill all active tests, set them as untested, and
1202 clear remaining tests.
1203 '''
1204 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +08001205 self.cancel_pending_tests()
Jon Salz57717ca2012-04-04 16:47:25 +08001206
Jon Salz0697cbf2012-07-04 15:14:04 +08001207 def handle_switch_test(self, event):
1208 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001209
Jon Salz0697cbf2012-07-04 15:14:04 +08001210 @param event: The SWITCH_TEST event.
1211 '''
1212 test = self.test_list.lookup_path(event.path)
1213 if not test:
1214 logging.error('Unknown test %r', event.key)
1215 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001216
Jon Salz0697cbf2012-07-04 15:14:04 +08001217 invoc = self.invocations.get(test)
1218 if invoc and test.backgroundable:
1219 # Already running: just bring to the front if it
1220 # has a UI.
1221 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001222 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001223 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001224
Jon Salz0697cbf2012-07-04 15:14:04 +08001225 self.abort_active_tests()
1226 for t in test.walk():
1227 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001228
Jon Salz0697cbf2012-07-04 15:14:04 +08001229 if self.test_list.options.auto_run_on_keypress:
1230 self.auto_run(starting_at=test)
1231 else:
1232 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001233
Jon Salz0697cbf2012-07-04 15:14:04 +08001234 def wait(self):
1235 '''Waits for all pending invocations.
1236
1237 Useful for testing.
1238 '''
Jon Salz1acc8742012-07-17 17:45:55 +08001239 while self.invocations:
1240 for k, v in self.invocations.iteritems():
1241 logging.info('Waiting for %s to complete...', k)
1242 v.thread.join()
1243 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001244
1245 def check_exceptions(self):
1246 '''Raises an error if any exceptions have occurred in
1247 invocation threads.'''
1248 if self.exceptions:
1249 raise RuntimeError('Exception in invocation thread: %r' %
1250 self.exceptions)
1251
1252 def record_exception(self, msg):
1253 '''Records an exception in an invocation thread.
1254
1255 An exception with the given message will be rethrown when
1256 Goofy is destroyed.'''
1257 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001258
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001259
1260if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001261 Goofy().main()