blob: d37cb941a98bad8bc2c35bad3428a084a59f331b [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 sys
Jon Salz0405ab52012-03-16 15:26:52 +080016import threading
17import time
18import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080019import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080020from collections import deque
21from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080022
Jon Salz0697cbf2012-07-04 15:14:04 +080023import factory_common # pylint: disable=W0611
Jon Salz83591782012-06-26 11:09:58 +080024from cros.factory.goofy.prespawner import Prespawner
25from cros.factory.test import factory
26from cros.factory.test import state
27from cros.factory.test.factory import TestState
28from cros.factory.goofy import updater
Jon Salz51528e12012-07-02 18:54:45 +080029from cros.factory.goofy import test_steps
30from cros.factory.goofy.event_log_watcher import EventLogWatcher
31from cros.factory.test import shopfloor
Jon Salz83591782012-06-26 11:09:58 +080032from cros.factory.test import utils
33from cros.factory.test.event import Event
34from cros.factory.test.event import EventClient
35from cros.factory.test.event import EventServer
Jon Salzee85d522012-07-17 14:34:46 +080036from cros.factory import event_log
Jon Salz83591782012-06-26 11:09:58 +080037from 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
Vic Yang4953fc12012-07-26 16:19:53 +080044from cros.factory.goofy.charge_manager import ChargeManager
Jon Salz78c32392012-07-25 14:18:29 +080045from cros.factory.utils.process_utils import Spawn
Hung-Te Linf2f78f72012-02-08 19:27:11 +080046
47
Jon Salz2f757d42012-06-27 17:06:42 +080048DEFAULT_TEST_LISTS_DIR = os.path.join(factory.FACTORY_PATH, 'test_lists')
49CUSTOM_DIR = os.path.join(factory.FACTORY_PATH, 'custom')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080050HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
51
Jon Salz8796e362012-05-24 11:39:09 +080052# File that suppresses reboot if present (e.g., for development).
53NO_REBOOT_FILE = '/var/log/factory.noreboot'
54
Jon Salz5c344f62012-07-13 14:31:16 +080055# Value for tests_after_shutdown that forces auto-run (e.g., after
56# a factory update, when the available set of tests might change).
57FORCE_AUTO_RUN = 'force_auto_run'
58
cychiang21886742012-07-05 15:16:32 +080059RUN_QUEUE_TIMEOUT_SECS = 10
60
Jon Salz758e6cc2012-04-03 15:47:07 +080061GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
62You are running Goofy inside the chroot. Autotests are not supported.
63
64To use Goofy in the chroot, first install an Xvnc server:
65
Jon Salz0697cbf2012-07-04 15:14:04 +080066 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080067
68...and then start a VNC X server outside the chroot:
69
Jon Salz0697cbf2012-07-04 15:14:04 +080070 vncserver :10 &
71 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080072
73...and run Goofy as follows:
74
Jon Salz0697cbf2012-07-04 15:14:04 +080075 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080076''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080077suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080078
79def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080080 '''
81 Returns the HWID config tag, or an empty string if none can be found.
82 '''
83 if 'CROS_HWID' in os.environ:
84 return os.environ['CROS_HWID']
85 if os.path.exists(HWID_CFG_PATH):
86 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
87 return hwid_cfg_handle.read().strip()
88 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080089
90
91def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080092 '''
93 Returns the path to the active test list, based on the HWID config tag.
94 '''
95 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080096
Jon Salz0697cbf2012-07-04 15:14:04 +080097 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080098
Jon Salz0697cbf2012-07-04 15:14:04 +080099 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
100 search_files = ['test_list', 'test_list.all']
101 if hwid_cfg:
102 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800103
Jon Salz0697cbf2012-07-04 15:14:04 +0800104 for d in search_dirs:
105 for f in search_files:
106 test_list = os.path.join(d, f)
107 if os.path.exists(test_list):
108 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800109
Jon Salz0697cbf2012-07-04 15:14:04 +0800110 logging.warn('Cannot find test lists named any of %s in any of %s',
111 search_files, search_dirs)
112 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800113
Jon Salz73e0fd02012-04-04 11:46:38 +0800114_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800115
116class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800117 '''
118 The main factory flow.
119
120 Note that all methods in this class must be invoked from the main
121 (event) thread. Other threads, such as callbacks and TestInvocation
122 methods, should instead post events on the run queue.
123
124 TODO: Unit tests. (chrome-os-partner:7409)
125
126 Properties:
127 uuid: A unique UUID for this invocation of Goofy.
128 state_instance: An instance of FactoryState.
129 state_server: The FactoryState XML/RPC server.
130 state_server_thread: A thread running state_server.
131 event_server: The EventServer socket server.
132 event_server_thread: A thread running event_server.
133 event_client: A client to the event server.
134 connection_manager: The connection_manager object.
135 network_enabled: Whether the connection_manager is currently
136 enabling connections.
137 ui_process: The factory ui process object.
138 run_queue: A queue of callbacks to invoke from the main thread.
139 invocations: A map from FactoryTest objects to the corresponding
140 TestInvocations objects representing active tests.
141 tests_to_run: A deque of tests that should be run when the current
142 test(s) complete.
143 options: Command-line options.
144 args: Command-line args.
145 test_list: The test list.
146 event_handlers: Map of Event.Type to the method used to handle that
147 event. If the method has an 'event' argument, the event is passed
148 to the handler.
149 exceptions: Exceptions encountered in invocation threads.
150 '''
151 def __init__(self):
152 self.uuid = str(uuid.uuid4())
153 self.state_instance = None
154 self.state_server = None
155 self.state_server_thread = None
Jon Salz16d10542012-07-23 12:18:45 +0800156 self.goofy_rpc = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800157 self.event_server = None
158 self.event_server_thread = None
159 self.event_client = None
160 self.connection_manager = None
Vic Yang4953fc12012-07-26 16:19:53 +0800161 self.charge_manager = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800162 self.time_sanitizer = None
163 self.time_synced = False
Jon Salz0697cbf2012-07-04 15:14:04 +0800164 self.log_watcher = None
165 self.network_enabled = True
166 self.event_log = None
167 self.prespawner = None
168 self.ui_process = None
169 self.run_queue = Queue.Queue()
170 self.invocations = {}
171 self.tests_to_run = deque()
172 self.visible_test = None
173 self.chrome = None
174
175 self.options = None
176 self.args = None
177 self.test_list = None
178 self.on_ui_startup = []
179 self.env = None
180 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800181 self.last_update_check = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800182 self.last_sync_time = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800183
Jon Salz85a39882012-07-05 16:45:04 +0800184 def test_or_root(event, parent_or_group=True):
185 '''Returns the test affected by a particular event.
186
187 Args:
188 event: The event containing an optional 'path' attribute.
189 parent_on_group: If True, returns the top-level parent for a test (the
190 root node of the tests that need to be run together if the given test
191 path is to be run).
192 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800193 try:
194 path = event.path
195 except AttributeError:
196 path = None
197
198 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800199 test = self.test_list.lookup_path(path)
200 if parent_or_group:
201 test = test.get_top_level_parent_or_group()
202 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800203 else:
204 return self.test_list
205
206 self.event_handlers = {
207 Event.Type.SWITCH_TEST: self.handle_switch_test,
208 Event.Type.SHOW_NEXT_ACTIVE_TEST:
209 lambda event: self.show_next_active_test(),
210 Event.Type.RESTART_TESTS:
211 lambda event: self.restart_tests(root=test_or_root(event)),
212 Event.Type.AUTO_RUN:
213 lambda event: self.auto_run(root=test_or_root(event)),
214 Event.Type.RE_RUN_FAILED:
215 lambda event: self.re_run_failed(root=test_or_root(event)),
216 Event.Type.RUN_TESTS_WITH_STATUS:
217 lambda event: self.run_tests_with_status(
218 event.status,
219 root=test_or_root(event)),
220 Event.Type.REVIEW:
221 lambda event: self.show_review_information(),
222 Event.Type.UPDATE_SYSTEM_INFO:
223 lambda event: self.update_system_info(),
224 Event.Type.UPDATE_FACTORY:
225 lambda event: self.update_factory(),
226 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800227 lambda event: self.stop(root=test_or_root(event, False),
228 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800229 Event.Type.SET_VISIBLE_TEST:
230 lambda event: self.set_visible_test(
231 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800232 }
233
234 self.exceptions = []
235 self.web_socket_manager = None
236
237 def destroy(self):
238 if self.chrome:
239 self.chrome.kill()
240 self.chrome = None
241 if self.ui_process:
242 utils.kill_process_tree(self.ui_process, 'ui')
243 self.ui_process = None
244 if self.web_socket_manager:
245 logging.info('Stopping web sockets')
246 self.web_socket_manager.close()
247 self.web_socket_manager = None
248 if self.state_server_thread:
249 logging.info('Stopping state server')
250 self.state_server.shutdown()
251 self.state_server_thread.join()
252 self.state_server.server_close()
253 self.state_server_thread = None
254 if self.state_instance:
255 self.state_instance.close()
256 if self.event_server_thread:
257 logging.info('Stopping event server')
258 self.event_server.shutdown() # pylint: disable=E1101
259 self.event_server_thread.join()
260 self.event_server.server_close()
261 self.event_server_thread = None
262 if self.log_watcher:
263 if self.log_watcher.IsThreadStarted():
264 self.log_watcher.StopWatchThread()
265 self.log_watcher = None
266 if self.prespawner:
267 logging.info('Stopping prespawner')
268 self.prespawner.stop()
269 self.prespawner = None
270 if self.event_client:
271 logging.info('Closing event client')
272 self.event_client.close()
273 self.event_client = None
274 if self.event_log:
275 self.event_log.Close()
276 self.event_log = None
277 self.check_exceptions()
278 logging.info('Done destroying Goofy')
279
280 def start_state_server(self):
281 self.state_instance, self.state_server = (
282 state.create_server(bind_address='0.0.0.0'))
Jon Salz16d10542012-07-23 12:18:45 +0800283 self.goofy_rpc = GoofyRPC(self)
284 self.goofy_rpc.RegisterMethods(self.state_instance)
Jon Salz0697cbf2012-07-04 15:14:04 +0800285 logging.info('Starting state server')
286 self.state_server_thread = threading.Thread(
287 target=self.state_server.serve_forever,
288 name='StateServer')
289 self.state_server_thread.start()
290
291 def start_event_server(self):
292 self.event_server = EventServer()
293 logging.info('Starting factory event server')
294 self.event_server_thread = threading.Thread(
295 target=self.event_server.serve_forever,
296 name='EventServer') # pylint: disable=E1101
297 self.event_server_thread.start()
298
299 self.event_client = EventClient(
300 callback=self.handle_event, event_loop=self.run_queue)
301
302 self.web_socket_manager = WebSocketManager(self.uuid)
303 self.state_server.add_handler("/event",
304 self.web_socket_manager.handle_web_socket)
305
306 def start_ui(self):
307 ui_proc_args = [
308 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
309 self.options.test_list]
310 if self.options.verbose:
311 ui_proc_args.append('-v')
312 logging.info('Starting ui %s', ui_proc_args)
Jon Salz78c32392012-07-25 14:18:29 +0800313 self.ui_process = Spawn(ui_proc_args)
Jon Salz0697cbf2012-07-04 15:14:04 +0800314 logging.info('Waiting for UI to come up...')
315 self.event_client.wait(
316 lambda event: event.type == Event.Type.UI_READY)
317 logging.info('UI has started')
318
319 def set_visible_test(self, test):
320 if self.visible_test == test:
321 return
Jon Salz2f2d42c2012-07-30 12:30:34 +0800322 if test and not test.has_ui:
323 return
Jon Salz0697cbf2012-07-04 15:14:04 +0800324
325 if test:
326 test.update_state(visible=True)
327 if self.visible_test:
328 self.visible_test.update_state(visible=False)
329 self.visible_test = test
330
331 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800332 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800333 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800334
Jon Salz0697cbf2012-07-04 15:14:04 +0800335 @param test: The ShutdownStep.
336 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800337 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800338 test_state = test.update_state(increment_shutdown_count=1)
339 logging.info('Detected shutdown (%d of %d)',
340 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800341
Jon Salz0697cbf2012-07-04 15:14:04 +0800342 def log_and_update_state(status, error_msg, **kw):
343 self.event_log.Log('rebooted',
344 status=status, error_msg=error_msg, **kw)
345 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800346
Jon Salz0697cbf2012-07-04 15:14:04 +0800347 if not self.last_shutdown_time:
348 log_and_update_state(status=TestState.FAILED,
349 error_msg='Unable to read shutdown_time')
350 return
Jon Salz258a40c2012-04-19 12:34:01 +0800351
Jon Salz0697cbf2012-07-04 15:14:04 +0800352 now = time.time()
353 logging.info('%.03f s passed since reboot',
354 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800355
Jon Salz0697cbf2012-07-04 15:14:04 +0800356 if self.last_shutdown_time > now:
357 test.update_state(status=TestState.FAILED,
358 error_msg='Time moved backward during reboot')
359 elif (isinstance(test, factory.RebootStep) and
360 self.test_list.options.max_reboot_time_secs and
361 (now - self.last_shutdown_time >
362 self.test_list.options.max_reboot_time_secs)):
363 # A reboot took too long; fail. (We don't check this for
364 # HaltSteps, because the machine could be halted for a
365 # very long time, and even unplugged with battery backup,
366 # thus hosing the clock.)
367 log_and_update_state(
368 status=TestState.FAILED,
369 error_msg=('More than %d s elapsed during reboot '
370 '(%.03f s, from %s to %s)' % (
371 self.test_list.options.max_reboot_time_secs,
372 now - self.last_shutdown_time,
373 utils.TimeString(self.last_shutdown_time),
374 utils.TimeString(now))),
375 duration=(now-self.last_shutdown_time))
376 elif test_state.shutdown_count == test.iterations:
377 # Good!
378 log_and_update_state(status=TestState.PASSED,
379 duration=(now - self.last_shutdown_time),
380 error_msg='')
381 elif test_state.shutdown_count > test.iterations:
382 # Shut down too many times
383 log_and_update_state(status=TestState.FAILED,
384 error_msg='Too many shutdowns')
385 elif utils.are_shift_keys_depressed():
386 logging.info('Shift keys are depressed; cancelling restarts')
387 # Abort shutdown
388 log_and_update_state(
389 status=TestState.FAILED,
390 error_msg='Shutdown aborted with double shift keys')
Jon Salza6711d72012-07-18 14:33:03 +0800391 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800392 else:
393 def handler():
394 if self._prompt_cancel_shutdown(
395 test, test_state.shutdown_count + 1):
Jon Salza6711d72012-07-18 14:33:03 +0800396 factory.console.info('Shutdown aborted by operator')
Jon Salz0697cbf2012-07-04 15:14:04 +0800397 log_and_update_state(
398 status=TestState.FAILED,
399 error_msg='Shutdown aborted by operator')
Jon Salza6711d72012-07-18 14:33:03 +0800400 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800401 return
Jon Salz0405ab52012-03-16 15:26:52 +0800402
Jon Salz0697cbf2012-07-04 15:14:04 +0800403 # Time to shutdown again
404 log_and_update_state(
405 status=TestState.ACTIVE,
406 error_msg='',
407 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800408
Jon Salz0697cbf2012-07-04 15:14:04 +0800409 self.event_log.Log('shutdown', operation='reboot')
410 self.state_instance.set_shared_data('shutdown_time',
411 time.time())
412 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800413
Jon Salz0697cbf2012-07-04 15:14:04 +0800414 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415
Jon Salz0697cbf2012-07-04 15:14:04 +0800416 def _prompt_cancel_shutdown(self, test, iteration):
417 if self.options.ui != 'chrome':
418 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800419
Jon Salz0697cbf2012-07-04 15:14:04 +0800420 pending_shutdown_data = {
421 'delay_secs': test.delay_secs,
422 'time': time.time() + test.delay_secs,
423 'operation': test.operation,
424 'iteration': iteration,
425 'iterations': test.iterations,
426 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800427
Jon Salz0697cbf2012-07-04 15:14:04 +0800428 # Create a new (threaded) event client since we
429 # don't want to use the event loop for this.
430 with EventClient() as event_client:
431 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
432 **pending_shutdown_data))
433 aborted = event_client.wait(
434 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
435 timeout=test.delay_secs) is not None
436 if aborted:
437 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
438 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800439
Jon Salz0697cbf2012-07-04 15:14:04 +0800440 def init_states(self):
441 '''
442 Initializes all states on startup.
443 '''
444 for test in self.test_list.get_all_tests():
445 # Make sure the state server knows about all the tests,
446 # defaulting to an untested state.
447 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800448
Jon Salz0697cbf2012-07-04 15:14:04 +0800449 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800450
Jon Salz0697cbf2012-07-04 15:14:04 +0800451 # Any 'active' tests should be marked as failed now.
452 for test in self.test_list.walk():
Jon Salza6711d72012-07-18 14:33:03 +0800453 if not test.is_leaf():
454 # Don't bother with parents; they will be updated when their
455 # children are updated.
456 continue
457
Jon Salz0697cbf2012-07-04 15:14:04 +0800458 test_state = test.get_state()
459 if test_state.status != TestState.ACTIVE:
460 continue
461 if isinstance(test, factory.ShutdownStep):
462 # Shutdown while the test was active - that's good.
463 self.handle_shutdown_complete(test, test_state)
464 else:
465 # Unexpected shutdown. Grab /var/log/messages for context.
466 if var_log_messages is None:
467 try:
468 var_log_messages = (
469 utils.var_log_messages_before_reboot())
470 # Write it to the log, to make it easier to
471 # correlate with /var/log/messages.
472 logging.info(
473 'Unexpected shutdown. '
474 'Tail of /var/log/messages before last reboot:\n'
475 '%s', ('\n'.join(
476 ' ' + x for x in var_log_messages)))
477 except: # pylint: disable=W0702
478 logging.exception('Unable to grok /var/log/messages')
479 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800480
Jon Salz0697cbf2012-07-04 15:14:04 +0800481 error_msg = 'Unexpected shutdown while test was running'
482 self.event_log.Log('end_test',
483 path=test.path,
484 status=TestState.FAILED,
485 invocation=test.get_state().invocation,
486 error_msg=error_msg,
487 var_log_messages='\n'.join(var_log_messages))
488 test.update_state(
489 status=TestState.FAILED,
490 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800491
Jon Salz50efe942012-07-26 11:54:10 +0800492 if not test.never_fails:
493 # For "never_fails" tests (such as "Start"), don't cancel
494 # pending tests, since reboot is expected.
495 factory.console.info('Unexpected shutdown while test %s '
496 'running; cancelling any pending tests',
497 test.path)
498 self.state_instance.set_shared_data('tests_after_shutdown', [])
Jon Salz69806bb2012-07-20 18:05:02 +0800499
Jon Salz0697cbf2012-07-04 15:14:04 +0800500 def show_next_active_test(self):
501 '''
502 Rotates to the next visible active test.
503 '''
504 self.reap_completed_tests()
505 active_tests = [
506 t for t in self.test_list.walk()
507 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
508 if not active_tests:
509 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800510
Jon Salz0697cbf2012-07-04 15:14:04 +0800511 try:
512 next_test = active_tests[
513 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
514 except ValueError: # visible_test not present in active_tests
515 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800516
Jon Salz0697cbf2012-07-04 15:14:04 +0800517 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800518
Jon Salz0697cbf2012-07-04 15:14:04 +0800519 def handle_event(self, event):
520 '''
521 Handles an event from the event server.
522 '''
523 handler = self.event_handlers.get(event.type)
524 if handler:
525 handler(event)
526 else:
527 # We don't register handlers for all event types - just ignore
528 # this event.
529 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800530
Jon Salz0697cbf2012-07-04 15:14:04 +0800531 def run_next_test(self):
532 '''
533 Runs the next eligible test (or tests) in self.tests_to_run.
534 '''
535 self.reap_completed_tests()
536 while self.tests_to_run:
537 logging.debug('Tests to run: %s',
538 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800539
Jon Salz0697cbf2012-07-04 15:14:04 +0800540 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800541
Jon Salz0697cbf2012-07-04 15:14:04 +0800542 if test in self.invocations:
543 logging.info('Next test %s is already running', test.path)
544 self.tests_to_run.popleft()
545 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800546
Jon Salza1412922012-07-23 16:04:17 +0800547 for requirement in test.require_run:
548 for i in requirement.test.walk():
549 if i.get_state().status == TestState.ACTIVE:
Jon Salz304a75d2012-07-06 11:14:15 +0800550 logging.info('Waiting for active test %s to complete '
Jon Salza1412922012-07-23 16:04:17 +0800551 'before running %s', i.path, test.path)
Jon Salz304a75d2012-07-06 11:14:15 +0800552 return
553
Jon Salz0697cbf2012-07-04 15:14:04 +0800554 if self.invocations and not (test.backgroundable and all(
555 [x.backgroundable for x in self.invocations])):
556 logging.debug('Waiting for non-backgroundable tests to '
557 'complete before running %s', test.path)
558 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800559
Jon Salz0697cbf2012-07-04 15:14:04 +0800560 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800561
Jon Salz304a75d2012-07-06 11:14:15 +0800562 untested = set()
Jon Salza1412922012-07-23 16:04:17 +0800563 for requirement in test.require_run:
564 for i in requirement.test.walk():
565 if i == test:
Jon Salz304a75d2012-07-06 11:14:15 +0800566 # We've hit this test itself; stop checking
567 break
Jon Salza1412922012-07-23 16:04:17 +0800568 if ((i.get_state().status == TestState.UNTESTED) or
569 (requirement.passed and i.get_state().status !=
570 TestState.PASSED)):
Jon Salz304a75d2012-07-06 11:14:15 +0800571 # Found an untested test; move on to the next
572 # element in require_run.
Jon Salza1412922012-07-23 16:04:17 +0800573 untested.add(i)
Jon Salz304a75d2012-07-06 11:14:15 +0800574 break
575
576 if untested:
577 untested_paths = ', '.join(sorted([x.path for x in untested]))
578 if self.state_instance.get_shared_data('engineering_mode',
579 optional=True):
580 # In engineering mode, we'll let it go.
581 factory.console.warn('In engineering mode; running '
582 '%s even though required tests '
583 '[%s] have not completed',
584 test.path, untested_paths)
585 else:
586 # Not in engineering mode; mark it failed.
587 error_msg = ('Required tests [%s] have not been run yet'
588 % untested_paths)
589 factory.console.error('Not running %s: %s',
590 test.path, error_msg)
591 test.update_state(status=TestState.FAILED,
592 error_msg=error_msg)
593 continue
594
Jon Salz0697cbf2012-07-04 15:14:04 +0800595 if isinstance(test, factory.ShutdownStep):
596 if os.path.exists(NO_REBOOT_FILE):
597 test.update_state(
598 status=TestState.FAILED, increment_count=1,
599 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800600 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800601 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800602
Jon Salz0697cbf2012-07-04 15:14:04 +0800603 test.update_state(status=TestState.ACTIVE, increment_count=1,
604 error_msg='', shutdown_count=0)
605 if self._prompt_cancel_shutdown(test, 1):
606 self.event_log.Log('reboot_cancelled')
607 test.update_state(
608 status=TestState.FAILED, increment_count=1,
609 error_msg='Shutdown aborted by operator',
610 shutdown_count=0)
611 return
Jon Salz2f757d42012-06-27 17:06:42 +0800612
Jon Salz0697cbf2012-07-04 15:14:04 +0800613 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800614 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800615 'tests_after_shutdown',
616 [t.path for t in self.tests_to_run])
617 # Save shutdown time
618 self.state_instance.set_shared_data('shutdown_time',
619 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800620
Jon Salz0697cbf2012-07-04 15:14:04 +0800621 with self.env.lock:
622 self.event_log.Log('shutdown', operation=test.operation)
623 shutdown_result = self.env.shutdown(test.operation)
624 if shutdown_result:
625 # That's all, folks!
626 self.run_queue.put(None)
627 return
628 else:
629 # Just pass (e.g., in the chroot).
630 test.update_state(status=TestState.PASSED)
631 self.state_instance.set_shared_data(
632 'tests_after_shutdown', None)
633 # Send event with no fields to indicate that there is no
634 # longer a pending shutdown.
635 self.event_client.post_event(Event(
636 Event.Type.PENDING_SHUTDOWN))
637 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800638
Jon Salz1acc8742012-07-17 17:45:55 +0800639 self._run_test(test, test.iterations)
640
641 def _run_test(self, test, iterations_left=None):
642 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
643 new_state = test.update_state(
644 status=TestState.ACTIVE, increment_count=1, error_msg='',
645 invocation=invoc.uuid, iterations_left=iterations_left)
646 invoc.count = new_state.count
647
648 self.invocations[test] = invoc
649 if self.visible_test is None and test.has_ui:
650 self.set_visible_test(test)
651 self.check_connection_manager()
652 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800653
Jon Salz0697cbf2012-07-04 15:14:04 +0800654 def check_connection_manager(self):
655 exclusive_tests = [
656 test.path
657 for test in self.invocations
658 if test.is_exclusive(
659 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
660 if exclusive_tests:
661 # Make sure networking is disabled.
662 if self.network_enabled:
663 logging.info('Disabling network, as requested by %s',
664 exclusive_tests)
665 self.connection_manager.DisableNetworking()
666 self.network_enabled = False
667 else:
668 # Make sure networking is enabled.
669 if not self.network_enabled:
670 logging.info('Re-enabling network')
671 self.connection_manager.EnableNetworking()
672 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800673
cychiang21886742012-07-05 15:16:32 +0800674 def check_for_updates(self):
675 '''
676 Schedules an asynchronous check for updates if necessary.
677 '''
678 if not self.test_list.options.update_period_secs:
679 # Not enabled.
680 return
681
682 now = time.time()
683 if self.last_update_check and (
684 now - self.last_update_check <
685 self.test_list.options.update_period_secs):
686 # Not yet time for another check.
687 return
688
689 self.last_update_check = now
690
691 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
692 if reached_shopfloor:
693 new_update_md5sum = md5sum if needs_update else None
694 if system.SystemInfo.update_md5sum != new_update_md5sum:
695 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
696 system.SystemInfo.update_md5sum = new_update_md5sum
697 self.run_queue.put(self.update_system_info)
698
699 updater.CheckForUpdateAsync(
700 handle_check_for_update,
701 self.test_list.options.shopfloor_timeout_secs)
702
Jon Salza6711d72012-07-18 14:33:03 +0800703 def cancel_pending_tests(self):
704 '''Cancels any tests in the run queue.'''
705 self.run_tests([])
706
Jon Salz0697cbf2012-07-04 15:14:04 +0800707 def run_tests(self, subtrees, untested_only=False):
708 '''
709 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800710
Jon Salz0697cbf2012-07-04 15:14:04 +0800711 The tests are run in order unless one fails (then stops).
712 Backgroundable tests are run simultaneously; when a foreground test is
713 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800714
Jon Salz0697cbf2012-07-04 15:14:04 +0800715 @param subtrees: Node or nodes containing tests to run (may either be
716 a single test or a list). Duplicates will be ignored.
717 '''
718 if type(subtrees) != list:
719 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800720
Jon Salz0697cbf2012-07-04 15:14:04 +0800721 # Nodes we've seen so far, to avoid duplicates.
722 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800723
Jon Salz0697cbf2012-07-04 15:14:04 +0800724 self.tests_to_run = deque()
725 for subtree in subtrees:
726 for test in subtree.walk():
727 if test in seen:
728 continue
729 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800730
Jon Salz0697cbf2012-07-04 15:14:04 +0800731 if not test.is_leaf():
732 continue
733 if (untested_only and
734 test.get_state().status != TestState.UNTESTED):
735 continue
736 self.tests_to_run.append(test)
737 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800738
Jon Salz0697cbf2012-07-04 15:14:04 +0800739 def reap_completed_tests(self):
740 '''
741 Removes completed tests from the set of active tests.
742
743 Also updates the visible test if it was reaped.
744 '''
745 for t, v in dict(self.invocations).iteritems():
746 if v.is_completed():
Jon Salz1acc8742012-07-17 17:45:55 +0800747 new_state = t.update_state(**v.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800748 del self.invocations[t]
749
Jon Salz1acc8742012-07-17 17:45:55 +0800750 if new_state.iterations_left and new_state.status == TestState.PASSED:
751 # Play it again, Sam!
752 self._run_test(t)
753
Jon Salz0697cbf2012-07-04 15:14:04 +0800754 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800755 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800756 self.set_visible_test(None)
757 # Make the first running test, if any, the visible test
758 for t in self.test_list.walk():
759 if t in self.invocations:
760 self.set_visible_test(t)
761 break
762
Jon Salz85a39882012-07-05 16:45:04 +0800763 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800764 '''
765 Kills and waits for all active tests.
766
Jon Salz85a39882012-07-05 16:45:04 +0800767 Args:
768 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800769 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800770 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800771 '''
772 self.reap_completed_tests()
773 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800774 if root and not test.has_ancestor(root):
775 continue
776
Jon Salz0697cbf2012-07-04 15:14:04 +0800777 factory.console.info('Killing active test %s...' % test.path)
778 invoc.abort_and_join()
779 factory.console.info('Killed %s' % test.path)
Jon Salz1acc8742012-07-17 17:45:55 +0800780 test.update_state(**invoc.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800781 del self.invocations[test]
Jon Salz1acc8742012-07-17 17:45:55 +0800782
Jon Salz0697cbf2012-07-04 15:14:04 +0800783 if not abort:
784 test.update_state(status=TestState.UNTESTED)
785 self.reap_completed_tests()
786
Jon Salz85a39882012-07-05 16:45:04 +0800787 def stop(self, root=None, fail=False):
788 self.kill_active_tests(fail, root)
789 # Remove any tests in the run queue under the root.
790 self.tests_to_run = deque([x for x in self.tests_to_run
791 if root and not x.has_ancestor(root)])
792 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800793
794 def abort_active_tests(self):
795 self.kill_active_tests(True)
796
797 def main(self):
798 try:
799 self.init()
800 self.event_log.Log('goofy_init',
801 success=True)
802 except:
803 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800804 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800805 self.event_log.Log('goofy_init',
806 success=False,
807 trace=traceback.format_exc())
808 except: # pylint: disable=W0702
809 pass
810 raise
811
812 self.run()
813
814 def update_system_info(self):
815 '''Updates system info.'''
816 system_info = system.SystemInfo()
817 self.state_instance.set_shared_data('system_info', system_info.__dict__)
818 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
819 system_info=system_info.__dict__))
820 logging.info('System info: %r', system_info.__dict__)
821
Jon Salz5c344f62012-07-13 14:31:16 +0800822 def update_factory(self, auto_run_on_restart=False):
823 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800824 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +0800825 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800826
Jon Salz5c344f62012-07-13 14:31:16 +0800827 def pre_update_hook():
828 if auto_run_on_restart:
829 self.state_instance.set_shared_data('tests_after_shutdown',
830 FORCE_AUTO_RUN)
831 self.state_instance.close()
832
Jon Salz0697cbf2012-07-04 15:14:04 +0800833 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800834 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800835 self.env.shutdown('reboot')
836 except: # pylint: disable=W0702
837 factory.console.exception('Unable to update')
838
839 def init(self, args=None, env=None):
840 '''Initializes Goofy.
841
842 Args:
843 args: A list of command-line arguments. Uses sys.argv if
844 args is None.
845 env: An Environment instance to use (or None to choose
846 FakeChrootEnvironment or DUTEnvironment as appropriate).
847 '''
848 parser = OptionParser()
849 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +0800850 action='store_true',
851 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +0800852 parser.add_option('--print_test_list', dest='print_test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800853 metavar='FILE',
854 help='Read and print test list FILE, and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +0800855 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +0800856 action='store_true',
857 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +0800858 parser.add_option('--ui', dest='ui', type='choice',
Jon Salz8fa8e832012-07-13 19:04:09 +0800859 choices=['none', 'gtk', 'chrome'],
860 default=('chrome' if utils.in_chroot() else 'gtk'),
861 help='UI to use')
Jon Salz0697cbf2012-07-04 15:14:04 +0800862 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
Jon Salz8fa8e832012-07-13 19:04:09 +0800863 type='int', default=1,
864 help=('Factor by which to scale UI '
865 '(Chrome UI only)'))
Jon Salz0697cbf2012-07-04 15:14:04 +0800866 parser.add_option('--test_list', dest='test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800867 metavar='FILE',
868 help='Use FILE as test list')
Jon Salz0697cbf2012-07-04 15:14:04 +0800869 (self.options, self.args) = parser.parse_args(args)
870
Jon Salz46b89562012-07-05 11:49:22 +0800871 # Make sure factory directories exist.
872 factory.get_log_root()
873 factory.get_state_root()
874 factory.get_test_data_root()
875
Jon Salz0697cbf2012-07-04 15:14:04 +0800876 global _inited_logging # pylint: disable=W0603
877 if not _inited_logging:
878 factory.init_logging('goofy', verbose=self.options.verbose)
879 _inited_logging = True
Jon Salz8fa8e832012-07-13 19:04:09 +0800880
Jon Salzee85d522012-07-17 14:34:46 +0800881 event_log.IncrementBootSequence()
Jon Salz0697cbf2012-07-04 15:14:04 +0800882 self.event_log = EventLog('goofy')
883
884 if (not suppress_chroot_warning and
885 factory.in_chroot() and
886 self.options.ui == 'gtk' and
887 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
888 # That's not going to work! Tell the user how to run
889 # this way.
890 logging.warn(GOOFY_IN_CHROOT_WARNING)
891 time.sleep(1)
892
893 if env:
894 self.env = env
895 elif factory.in_chroot():
896 self.env = test_environment.FakeChrootEnvironment()
897 logging.warn(
898 'Using chroot environment: will not actually run autotests')
899 else:
900 self.env = test_environment.DUTEnvironment()
901 self.env.goofy = self
902
903 if self.options.restart:
904 state.clear_state()
905
906 if self.options.print_test_list:
907 print (factory.read_test_list(
908 self.options.print_test_list,
909 test_classes=dict(test_steps.__dict__)).
910 __repr__(recursive=True))
911 return
912
913 if self.options.ui_scale_factor != 1 and utils.in_qemu():
914 logging.warn(
915 'In QEMU; ignoring ui_scale_factor argument')
916 self.options.ui_scale_factor = 1
917
918 logging.info('Started')
919
920 self.start_state_server()
921 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
922 self.state_instance.set_shared_data('ui_scale_factor',
923 self.options.ui_scale_factor)
924 self.last_shutdown_time = (
925 self.state_instance.get_shared_data('shutdown_time', optional=True))
926 self.state_instance.del_shared_data('shutdown_time', optional=True)
927
928 if not self.options.test_list:
929 self.options.test_list = find_test_list()
930 if not self.options.test_list:
931 logging.error('No test list. Aborting.')
932 sys.exit(1)
933 logging.info('Using test list %s', self.options.test_list)
934
935 self.test_list = factory.read_test_list(
936 self.options.test_list,
937 self.state_instance,
938 test_classes=dict(test_steps.__dict__))
939 if not self.state_instance.has_shared_data('ui_lang'):
940 self.state_instance.set_shared_data('ui_lang',
941 self.test_list.options.ui_lang)
942 self.state_instance.set_shared_data(
943 'test_list_options',
944 self.test_list.options.__dict__)
945 self.state_instance.test_list = self.test_list
946
Jon Salz8fa8e832012-07-13 19:04:09 +0800947 if self.test_list.options.time_sanitizer:
948 self.time_sanitizer = time_sanitizer.TimeSanitizer(
949 base_time=time_sanitizer.GetBaseTimeFromFile(
950 # lsb-factory is written by the factory install shim during
951 # installation, so it should have a good time obtained from
952 # the mini-Omaha server.
953 '/usr/local/etc/lsb-factory'))
954 self.time_sanitizer.RunOnce()
955
Jon Salz0697cbf2012-07-04 15:14:04 +0800956 self.init_states()
957 self.start_event_server()
958 self.connection_manager = self.env.create_connection_manager(
959 self.test_list.options.wlans)
960 # Note that we create a log watcher even if
961 # sync_event_log_period_secs isn't set (no background
962 # syncing), since we may use it to flush event logs as well.
963 self.log_watcher = EventLogWatcher(
964 self.test_list.options.sync_event_log_period_secs,
Jon Salz16d10542012-07-23 12:18:45 +0800965 handle_event_logs_callback=self.handle_event_logs)
Jon Salz0697cbf2012-07-04 15:14:04 +0800966 if self.test_list.options.sync_event_log_period_secs:
967 self.log_watcher.StartWatchThread()
968
969 self.update_system_info()
970
Vic Yang4953fc12012-07-26 16:19:53 +0800971 assert ((self.test_list.options.min_charge_pct is None) ==
972 (self.test_list.options.max_charge_pct is None))
973 if (self.test_list.options.min_charge_pct and
974 self.test_list.options.max_charge_pct):
975 self.charge_manager = ChargeManager(self.test_list.options.min_charge_pct,
976 self.test_list.options.max_charge_pct)
977
Jon Salz0697cbf2012-07-04 15:14:04 +0800978 os.environ['CROS_FACTORY'] = '1'
979 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
980
981 # Set CROS_UI since some behaviors in ui.py depend on the
982 # particular UI in use. TODO(jsalz): Remove this (and all
983 # places it is used) when the GTK UI is removed.
984 os.environ['CROS_UI'] = self.options.ui
985
986 if self.options.ui == 'chrome':
987 self.env.launch_chrome()
988 logging.info('Waiting for a web socket connection')
989 self.web_socket_manager.wait()
990
991 # Wait for the test widget size to be set; this is done in
992 # an asynchronous RPC so there is a small chance that the
993 # web socket might be opened first.
994 for _ in range(100): # 10 s
995 try:
996 if self.state_instance.get_shared_data('test_widget_size'):
997 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800998 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800999 pass # Retry
1000 time.sleep(0.1) # 100 ms
1001 else:
1002 logging.warn('Never received test_widget_size from UI')
1003 elif self.options.ui == 'gtk':
1004 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001005
Jon Salz0697cbf2012-07-04 15:14:04 +08001006 def state_change_callback(test, test_state):
1007 self.event_client.post_event(
1008 Event(Event.Type.STATE_CHANGE,
1009 path=test.path, state=test_state))
1010 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +08001011
Jon Salza6711d72012-07-18 14:33:03 +08001012 for handler in self.on_ui_startup:
1013 handler()
1014
1015 self.prespawner = Prespawner()
1016 self.prespawner.start()
1017
Jon Salz0697cbf2012-07-04 15:14:04 +08001018 try:
1019 tests_after_shutdown = self.state_instance.get_shared_data(
1020 'tests_after_shutdown')
1021 except KeyError:
1022 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +08001023
Jon Salz5c344f62012-07-13 14:31:16 +08001024 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
1025 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +08001026 logging.info('Resuming tests after shutdown: %s',
1027 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +08001028 self.tests_to_run.extend(
1029 self.test_list.lookup_path(t) for t in tests_after_shutdown)
1030 self.run_queue.put(self.run_next_test)
1031 else:
Jon Salz5c344f62012-07-13 14:31:16 +08001032 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +08001033 self.run_queue.put(
1034 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +08001035 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001036
Jon Salz0697cbf2012-07-04 15:14:04 +08001037 def run(self):
1038 '''Runs Goofy.'''
1039 # Process events forever.
1040 while self.run_once(True):
1041 pass
Jon Salz73e0fd02012-04-04 11:46:38 +08001042
Jon Salz0697cbf2012-07-04 15:14:04 +08001043 def run_once(self, block=False):
1044 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001045
Jon Salz0697cbf2012-07-04 15:14:04 +08001046 Args:
1047 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +08001048
Jon Salz0697cbf2012-07-04 15:14:04 +08001049 Returns:
1050 True to keep going or False to shut down.
1051 '''
1052 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +08001053 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +08001054 # Nothing on the run queue.
1055 self._run_queue_idle()
1056 if block:
1057 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +08001058 try:
1059 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
1060 except Queue.Empty:
1061 # Keep going (calling _run_queue_idle() again at the top of
1062 # the loop)
1063 continue
Jon Salz0697cbf2012-07-04 15:14:04 +08001064 # ...and grab anything else that showed up at the same
1065 # time.
1066 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +08001067 else:
1068 break
Jon Salz51528e12012-07-02 18:54:45 +08001069
Jon Salz0697cbf2012-07-04 15:14:04 +08001070 for event in events:
1071 if not event:
1072 # Shutdown request.
1073 self.run_queue.task_done()
1074 return False
Jon Salz51528e12012-07-02 18:54:45 +08001075
Jon Salz0697cbf2012-07-04 15:14:04 +08001076 try:
1077 event()
Jon Salz85a39882012-07-05 16:45:04 +08001078 except: # pylint: disable=W0702
1079 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001080 self.record_exception(traceback.format_exception_only(
1081 *sys.exc_info()[:2]))
1082 # But keep going
1083 finally:
1084 self.run_queue.task_done()
1085 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001086
Jon Salz8fa8e832012-07-13 19:04:09 +08001087 def sync_time_in_background(self):
1088 '''Attempts to sync time with the shopfloor server.'''
1089 if ((not self.test_list.options.sync_time_period_secs) or
1090 (not self.time_sanitizer) or
1091 self.time_synced or
1092 factory.in_chroot()):
1093 # Not enabled or already succeeded.
1094 return
1095
1096 now = time.time()
1097 if self.last_sync_time and (
1098 now - self.last_sync_time <
1099 self.test_list.options.sync_time_period_secs):
1100 # Not yet time for another check.
1101 return
1102 self.last_sync_time = now
1103
1104 def target():
1105 try:
1106 self.time_sanitizer.SyncWithShopfloor()
1107 self.time_synced = True
1108 except: # pylint: disable=W0702
1109 # Oh well. Log an error (but no trace)
1110 logging.info(
1111 'Unable to get time from shopfloor server: %s',
1112 utils.FormatExceptionOnly())
1113
1114 thread = threading.Thread(target=target)
1115 thread.daemon = True
1116 thread.start()
1117
Jon Salz0697cbf2012-07-04 15:14:04 +08001118 def _run_queue_idle(self):
Vic Yang4953fc12012-07-26 16:19:53 +08001119 '''Invoked when the run queue has no events.
1120
1121 This method must not raise exception.
1122 '''
Jon Salz0697cbf2012-07-04 15:14:04 +08001123 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001124 self.check_for_updates()
Jon Salz8fa8e832012-07-13 19:04:09 +08001125 self.sync_time_in_background()
Vic Yang4953fc12012-07-26 16:19:53 +08001126 if self.charge_manager:
1127 self.charge_manager.AdjustChargeState()
Jon Salz57717ca2012-04-04 16:47:25 +08001128
Jon Salz16d10542012-07-23 12:18:45 +08001129 def handle_event_logs(self, log_name, chunk):
Jon Salz0697cbf2012-07-04 15:14:04 +08001130 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001131
Jon Salz0697cbf2012-07-04 15:14:04 +08001132 Attempts to upload the event logs to the shopfloor server.
1133 '''
1134 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1135 start_time = time.time()
1136 logging.info('Syncing %s', description)
1137 shopfloor_client = shopfloor.get_instance(
1138 detect=True,
1139 timeout=self.test_list.options.shopfloor_timeout_secs)
1140 shopfloor_client.UploadEvent(log_name, chunk)
1141 logging.info(
1142 'Successfully synced %s in %.03f s',
1143 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001144
Jon Salz0697cbf2012-07-04 15:14:04 +08001145 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1146 root=None):
1147 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001148
Jon Salz0697cbf2012-07-04 15:14:04 +08001149 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001150
Jon Salz0697cbf2012-07-04 15:14:04 +08001151 Args:
1152 starting_at: If provided, only auto-runs tests beginning with
1153 this test.
1154 '''
1155 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001156
Jon Salz0697cbf2012-07-04 15:14:04 +08001157 if starting_at:
1158 # Make sure they passed a test, not a string.
1159 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001160
Jon Salz0697cbf2012-07-04 15:14:04 +08001161 tests_to_reset = []
1162 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001163
Jon Salz0697cbf2012-07-04 15:14:04 +08001164 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001165
Jon Salz0697cbf2012-07-04 15:14:04 +08001166 for test in root.get_top_level_tests():
1167 if starting_at:
1168 if test == starting_at:
1169 # We've found starting_at; do auto-run on all
1170 # subsequent tests.
1171 found_starting_at = True
1172 if not found_starting_at:
1173 # Don't start this guy yet
1174 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001175
Jon Salz0697cbf2012-07-04 15:14:04 +08001176 status = test.get_state().status
1177 if status == TestState.ACTIVE or status in statuses_to_run:
1178 # Reset the test (later; we will need to abort
1179 # all active tests first).
1180 tests_to_reset.append(test)
1181 if status in statuses_to_run:
1182 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001183
Jon Salz0697cbf2012-07-04 15:14:04 +08001184 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001185
Jon Salz0697cbf2012-07-04 15:14:04 +08001186 # Reset all statuses of the tests to run (in case any tests were active;
1187 # we want them to be run again).
1188 for test_to_reset in tests_to_reset:
1189 for test in test_to_reset.walk():
1190 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001191
Jon Salz0697cbf2012-07-04 15:14:04 +08001192 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001193
Jon Salz0697cbf2012-07-04 15:14:04 +08001194 def restart_tests(self, root=None):
1195 '''Restarts all tests.'''
1196 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001197
Jon Salz0697cbf2012-07-04 15:14:04 +08001198 self.abort_active_tests()
1199 for test in root.walk():
1200 test.update_state(status=TestState.UNTESTED)
1201 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001202
Jon Salz0697cbf2012-07-04 15:14:04 +08001203 def auto_run(self, starting_at=None, root=None):
1204 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001205
Jon Salz0697cbf2012-07-04 15:14:04 +08001206 Args:
1207 starting_at: If provide, only auto-runs tests beginning with
1208 this test.
1209 '''
1210 root = root or self.test_list
1211 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1212 starting_at=starting_at,
1213 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001214
Jon Salz0697cbf2012-07-04 15:14:04 +08001215 def re_run_failed(self, root=None):
1216 '''Re-runs failed tests.'''
1217 root = root or self.test_list
1218 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001219
Jon Salz0697cbf2012-07-04 15:14:04 +08001220 def show_review_information(self):
1221 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001222
Jon Salz0697cbf2012-07-04 15:14:04 +08001223 The information screene is rendered by main UI program (ui.py), so in
1224 goofy we only need to kill all active tests, set them as untested, and
1225 clear remaining tests.
1226 '''
1227 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +08001228 self.cancel_pending_tests()
Jon Salz57717ca2012-04-04 16:47:25 +08001229
Jon Salz0697cbf2012-07-04 15:14:04 +08001230 def handle_switch_test(self, event):
1231 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001232
Jon Salz0697cbf2012-07-04 15:14:04 +08001233 @param event: The SWITCH_TEST event.
1234 '''
1235 test = self.test_list.lookup_path(event.path)
1236 if not test:
1237 logging.error('Unknown test %r', event.key)
1238 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001239
Jon Salz0697cbf2012-07-04 15:14:04 +08001240 invoc = self.invocations.get(test)
1241 if invoc and test.backgroundable:
1242 # Already running: just bring to the front if it
1243 # has a UI.
1244 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001245 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001246 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001247
Jon Salz0697cbf2012-07-04 15:14:04 +08001248 self.abort_active_tests()
1249 for t in test.walk():
1250 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001251
Jon Salz0697cbf2012-07-04 15:14:04 +08001252 if self.test_list.options.auto_run_on_keypress:
1253 self.auto_run(starting_at=test)
1254 else:
1255 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001256
Jon Salz0697cbf2012-07-04 15:14:04 +08001257 def wait(self):
1258 '''Waits for all pending invocations.
1259
1260 Useful for testing.
1261 '''
Jon Salz1acc8742012-07-17 17:45:55 +08001262 while self.invocations:
1263 for k, v in self.invocations.iteritems():
1264 logging.info('Waiting for %s to complete...', k)
1265 v.thread.join()
1266 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001267
1268 def check_exceptions(self):
1269 '''Raises an error if any exceptions have occurred in
1270 invocation threads.'''
1271 if self.exceptions:
1272 raise RuntimeError('Exception in invocation thread: %r' %
1273 self.exceptions)
1274
1275 def record_exception(self, msg):
1276 '''Records an exception in an invocation thread.
1277
1278 An exception with the given message will be rethrown when
1279 Goofy is destroyed.'''
1280 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001281
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001282
1283if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001284 Goofy().main()