blob: 5f39549b6157905762ccf4e2ef71a3f21efdc8c6 [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
322
323 if test:
324 test.update_state(visible=True)
325 if self.visible_test:
326 self.visible_test.update_state(visible=False)
327 self.visible_test = test
328
329 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800330 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800331 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800332
Jon Salz0697cbf2012-07-04 15:14:04 +0800333 @param test: The ShutdownStep.
334 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800335 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800336 test_state = test.update_state(increment_shutdown_count=1)
337 logging.info('Detected shutdown (%d of %d)',
338 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800339
Jon Salz0697cbf2012-07-04 15:14:04 +0800340 def log_and_update_state(status, error_msg, **kw):
341 self.event_log.Log('rebooted',
342 status=status, error_msg=error_msg, **kw)
343 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800344
Jon Salz0697cbf2012-07-04 15:14:04 +0800345 if not self.last_shutdown_time:
346 log_and_update_state(status=TestState.FAILED,
347 error_msg='Unable to read shutdown_time')
348 return
Jon Salz258a40c2012-04-19 12:34:01 +0800349
Jon Salz0697cbf2012-07-04 15:14:04 +0800350 now = time.time()
351 logging.info('%.03f s passed since reboot',
352 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800353
Jon Salz0697cbf2012-07-04 15:14:04 +0800354 if self.last_shutdown_time > now:
355 test.update_state(status=TestState.FAILED,
356 error_msg='Time moved backward during reboot')
357 elif (isinstance(test, factory.RebootStep) and
358 self.test_list.options.max_reboot_time_secs and
359 (now - self.last_shutdown_time >
360 self.test_list.options.max_reboot_time_secs)):
361 # A reboot took too long; fail. (We don't check this for
362 # HaltSteps, because the machine could be halted for a
363 # very long time, and even unplugged with battery backup,
364 # thus hosing the clock.)
365 log_and_update_state(
366 status=TestState.FAILED,
367 error_msg=('More than %d s elapsed during reboot '
368 '(%.03f s, from %s to %s)' % (
369 self.test_list.options.max_reboot_time_secs,
370 now - self.last_shutdown_time,
371 utils.TimeString(self.last_shutdown_time),
372 utils.TimeString(now))),
373 duration=(now-self.last_shutdown_time))
374 elif test_state.shutdown_count == test.iterations:
375 # Good!
376 log_and_update_state(status=TestState.PASSED,
377 duration=(now - self.last_shutdown_time),
378 error_msg='')
379 elif test_state.shutdown_count > test.iterations:
380 # Shut down too many times
381 log_and_update_state(status=TestState.FAILED,
382 error_msg='Too many shutdowns')
383 elif utils.are_shift_keys_depressed():
384 logging.info('Shift keys are depressed; cancelling restarts')
385 # Abort shutdown
386 log_and_update_state(
387 status=TestState.FAILED,
388 error_msg='Shutdown aborted with double shift keys')
Jon Salza6711d72012-07-18 14:33:03 +0800389 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800390 else:
391 def handler():
392 if self._prompt_cancel_shutdown(
393 test, test_state.shutdown_count + 1):
Jon Salza6711d72012-07-18 14:33:03 +0800394 factory.console.info('Shutdown aborted by operator')
Jon Salz0697cbf2012-07-04 15:14:04 +0800395 log_and_update_state(
396 status=TestState.FAILED,
397 error_msg='Shutdown aborted by operator')
Jon Salza6711d72012-07-18 14:33:03 +0800398 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800399 return
Jon Salz0405ab52012-03-16 15:26:52 +0800400
Jon Salz0697cbf2012-07-04 15:14:04 +0800401 # Time to shutdown again
402 log_and_update_state(
403 status=TestState.ACTIVE,
404 error_msg='',
405 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800406
Jon Salz0697cbf2012-07-04 15:14:04 +0800407 self.event_log.Log('shutdown', operation='reboot')
408 self.state_instance.set_shared_data('shutdown_time',
409 time.time())
410 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800411
Jon Salz0697cbf2012-07-04 15:14:04 +0800412 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800413
Jon Salz0697cbf2012-07-04 15:14:04 +0800414 def _prompt_cancel_shutdown(self, test, iteration):
415 if self.options.ui != 'chrome':
416 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800417
Jon Salz0697cbf2012-07-04 15:14:04 +0800418 pending_shutdown_data = {
419 'delay_secs': test.delay_secs,
420 'time': time.time() + test.delay_secs,
421 'operation': test.operation,
422 'iteration': iteration,
423 'iterations': test.iterations,
424 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800425
Jon Salz0697cbf2012-07-04 15:14:04 +0800426 # Create a new (threaded) event client since we
427 # don't want to use the event loop for this.
428 with EventClient() as event_client:
429 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
430 **pending_shutdown_data))
431 aborted = event_client.wait(
432 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
433 timeout=test.delay_secs) is not None
434 if aborted:
435 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
436 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800437
Jon Salz0697cbf2012-07-04 15:14:04 +0800438 def init_states(self):
439 '''
440 Initializes all states on startup.
441 '''
442 for test in self.test_list.get_all_tests():
443 # Make sure the state server knows about all the tests,
444 # defaulting to an untested state.
445 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800446
Jon Salz0697cbf2012-07-04 15:14:04 +0800447 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800448
Jon Salz0697cbf2012-07-04 15:14:04 +0800449 # Any 'active' tests should be marked as failed now.
450 for test in self.test_list.walk():
Jon Salza6711d72012-07-18 14:33:03 +0800451 if not test.is_leaf():
452 # Don't bother with parents; they will be updated when their
453 # children are updated.
454 continue
455
Jon Salz0697cbf2012-07-04 15:14:04 +0800456 test_state = test.get_state()
457 if test_state.status != TestState.ACTIVE:
458 continue
459 if isinstance(test, factory.ShutdownStep):
460 # Shutdown while the test was active - that's good.
461 self.handle_shutdown_complete(test, test_state)
462 else:
463 # Unexpected shutdown. Grab /var/log/messages for context.
464 if var_log_messages is None:
465 try:
466 var_log_messages = (
467 utils.var_log_messages_before_reboot())
468 # Write it to the log, to make it easier to
469 # correlate with /var/log/messages.
470 logging.info(
471 'Unexpected shutdown. '
472 'Tail of /var/log/messages before last reboot:\n'
473 '%s', ('\n'.join(
474 ' ' + x for x in var_log_messages)))
475 except: # pylint: disable=W0702
476 logging.exception('Unable to grok /var/log/messages')
477 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800478
Jon Salz0697cbf2012-07-04 15:14:04 +0800479 error_msg = 'Unexpected shutdown while test was running'
480 self.event_log.Log('end_test',
481 path=test.path,
482 status=TestState.FAILED,
483 invocation=test.get_state().invocation,
484 error_msg=error_msg,
485 var_log_messages='\n'.join(var_log_messages))
486 test.update_state(
487 status=TestState.FAILED,
488 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800489
Jon Salz50efe942012-07-26 11:54:10 +0800490 if not test.never_fails:
491 # For "never_fails" tests (such as "Start"), don't cancel
492 # pending tests, since reboot is expected.
493 factory.console.info('Unexpected shutdown while test %s '
494 'running; cancelling any pending tests',
495 test.path)
496 self.state_instance.set_shared_data('tests_after_shutdown', [])
Jon Salz69806bb2012-07-20 18:05:02 +0800497
Jon Salz0697cbf2012-07-04 15:14:04 +0800498 def show_next_active_test(self):
499 '''
500 Rotates to the next visible active test.
501 '''
502 self.reap_completed_tests()
503 active_tests = [
504 t for t in self.test_list.walk()
505 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
506 if not active_tests:
507 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800508
Jon Salz0697cbf2012-07-04 15:14:04 +0800509 try:
510 next_test = active_tests[
511 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
512 except ValueError: # visible_test not present in active_tests
513 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800514
Jon Salz0697cbf2012-07-04 15:14:04 +0800515 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800516
Jon Salz0697cbf2012-07-04 15:14:04 +0800517 def handle_event(self, event):
518 '''
519 Handles an event from the event server.
520 '''
521 handler = self.event_handlers.get(event.type)
522 if handler:
523 handler(event)
524 else:
525 # We don't register handlers for all event types - just ignore
526 # this event.
527 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800528
Jon Salz0697cbf2012-07-04 15:14:04 +0800529 def run_next_test(self):
530 '''
531 Runs the next eligible test (or tests) in self.tests_to_run.
532 '''
533 self.reap_completed_tests()
534 while self.tests_to_run:
535 logging.debug('Tests to run: %s',
536 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800537
Jon Salz0697cbf2012-07-04 15:14:04 +0800538 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800539
Jon Salz0697cbf2012-07-04 15:14:04 +0800540 if test in self.invocations:
541 logging.info('Next test %s is already running', test.path)
542 self.tests_to_run.popleft()
543 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800544
Jon Salza1412922012-07-23 16:04:17 +0800545 for requirement in test.require_run:
546 for i in requirement.test.walk():
547 if i.get_state().status == TestState.ACTIVE:
Jon Salz304a75d2012-07-06 11:14:15 +0800548 logging.info('Waiting for active test %s to complete '
Jon Salza1412922012-07-23 16:04:17 +0800549 'before running %s', i.path, test.path)
Jon Salz304a75d2012-07-06 11:14:15 +0800550 return
551
Jon Salz0697cbf2012-07-04 15:14:04 +0800552 if self.invocations and not (test.backgroundable and all(
553 [x.backgroundable for x in self.invocations])):
554 logging.debug('Waiting for non-backgroundable tests to '
555 'complete before running %s', test.path)
556 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800557
Jon Salz0697cbf2012-07-04 15:14:04 +0800558 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800559
Jon Salz304a75d2012-07-06 11:14:15 +0800560 untested = set()
Jon Salza1412922012-07-23 16:04:17 +0800561 for requirement in test.require_run:
562 for i in requirement.test.walk():
563 if i == test:
Jon Salz304a75d2012-07-06 11:14:15 +0800564 # We've hit this test itself; stop checking
565 break
Jon Salza1412922012-07-23 16:04:17 +0800566 if ((i.get_state().status == TestState.UNTESTED) or
567 (requirement.passed and i.get_state().status !=
568 TestState.PASSED)):
Jon Salz304a75d2012-07-06 11:14:15 +0800569 # Found an untested test; move on to the next
570 # element in require_run.
Jon Salza1412922012-07-23 16:04:17 +0800571 untested.add(i)
Jon Salz304a75d2012-07-06 11:14:15 +0800572 break
573
574 if untested:
575 untested_paths = ', '.join(sorted([x.path for x in untested]))
576 if self.state_instance.get_shared_data('engineering_mode',
577 optional=True):
578 # In engineering mode, we'll let it go.
579 factory.console.warn('In engineering mode; running '
580 '%s even though required tests '
581 '[%s] have not completed',
582 test.path, untested_paths)
583 else:
584 # Not in engineering mode; mark it failed.
585 error_msg = ('Required tests [%s] have not been run yet'
586 % untested_paths)
587 factory.console.error('Not running %s: %s',
588 test.path, error_msg)
589 test.update_state(status=TestState.FAILED,
590 error_msg=error_msg)
591 continue
592
Jon Salz0697cbf2012-07-04 15:14:04 +0800593 if isinstance(test, factory.ShutdownStep):
594 if os.path.exists(NO_REBOOT_FILE):
595 test.update_state(
596 status=TestState.FAILED, increment_count=1,
597 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800598 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800599 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800600
Jon Salz0697cbf2012-07-04 15:14:04 +0800601 test.update_state(status=TestState.ACTIVE, increment_count=1,
602 error_msg='', shutdown_count=0)
603 if self._prompt_cancel_shutdown(test, 1):
604 self.event_log.Log('reboot_cancelled')
605 test.update_state(
606 status=TestState.FAILED, increment_count=1,
607 error_msg='Shutdown aborted by operator',
608 shutdown_count=0)
609 return
Jon Salz2f757d42012-06-27 17:06:42 +0800610
Jon Salz0697cbf2012-07-04 15:14:04 +0800611 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800612 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800613 'tests_after_shutdown',
614 [t.path for t in self.tests_to_run])
615 # Save shutdown time
616 self.state_instance.set_shared_data('shutdown_time',
617 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800618
Jon Salz0697cbf2012-07-04 15:14:04 +0800619 with self.env.lock:
620 self.event_log.Log('shutdown', operation=test.operation)
621 shutdown_result = self.env.shutdown(test.operation)
622 if shutdown_result:
623 # That's all, folks!
624 self.run_queue.put(None)
625 return
626 else:
627 # Just pass (e.g., in the chroot).
628 test.update_state(status=TestState.PASSED)
629 self.state_instance.set_shared_data(
630 'tests_after_shutdown', None)
631 # Send event with no fields to indicate that there is no
632 # longer a pending shutdown.
633 self.event_client.post_event(Event(
634 Event.Type.PENDING_SHUTDOWN))
635 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800636
Jon Salz1acc8742012-07-17 17:45:55 +0800637 self._run_test(test, test.iterations)
638
639 def _run_test(self, test, iterations_left=None):
640 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
641 new_state = test.update_state(
642 status=TestState.ACTIVE, increment_count=1, error_msg='',
643 invocation=invoc.uuid, iterations_left=iterations_left)
644 invoc.count = new_state.count
645
646 self.invocations[test] = invoc
647 if self.visible_test is None and test.has_ui:
648 self.set_visible_test(test)
649 self.check_connection_manager()
650 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800651
Jon Salz0697cbf2012-07-04 15:14:04 +0800652 def check_connection_manager(self):
653 exclusive_tests = [
654 test.path
655 for test in self.invocations
656 if test.is_exclusive(
657 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
658 if exclusive_tests:
659 # Make sure networking is disabled.
660 if self.network_enabled:
661 logging.info('Disabling network, as requested by %s',
662 exclusive_tests)
663 self.connection_manager.DisableNetworking()
664 self.network_enabled = False
665 else:
666 # Make sure networking is enabled.
667 if not self.network_enabled:
668 logging.info('Re-enabling network')
669 self.connection_manager.EnableNetworking()
670 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800671
cychiang21886742012-07-05 15:16:32 +0800672 def check_for_updates(self):
673 '''
674 Schedules an asynchronous check for updates if necessary.
675 '''
676 if not self.test_list.options.update_period_secs:
677 # Not enabled.
678 return
679
680 now = time.time()
681 if self.last_update_check and (
682 now - self.last_update_check <
683 self.test_list.options.update_period_secs):
684 # Not yet time for another check.
685 return
686
687 self.last_update_check = now
688
689 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
690 if reached_shopfloor:
691 new_update_md5sum = md5sum if needs_update else None
692 if system.SystemInfo.update_md5sum != new_update_md5sum:
693 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
694 system.SystemInfo.update_md5sum = new_update_md5sum
695 self.run_queue.put(self.update_system_info)
696
697 updater.CheckForUpdateAsync(
698 handle_check_for_update,
699 self.test_list.options.shopfloor_timeout_secs)
700
Jon Salza6711d72012-07-18 14:33:03 +0800701 def cancel_pending_tests(self):
702 '''Cancels any tests in the run queue.'''
703 self.run_tests([])
704
Jon Salz0697cbf2012-07-04 15:14:04 +0800705 def run_tests(self, subtrees, untested_only=False):
706 '''
707 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800708
Jon Salz0697cbf2012-07-04 15:14:04 +0800709 The tests are run in order unless one fails (then stops).
710 Backgroundable tests are run simultaneously; when a foreground test is
711 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800712
Jon Salz0697cbf2012-07-04 15:14:04 +0800713 @param subtrees: Node or nodes containing tests to run (may either be
714 a single test or a list). Duplicates will be ignored.
715 '''
716 if type(subtrees) != list:
717 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800718
Jon Salz0697cbf2012-07-04 15:14:04 +0800719 # Nodes we've seen so far, to avoid duplicates.
720 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800721
Jon Salz0697cbf2012-07-04 15:14:04 +0800722 self.tests_to_run = deque()
723 for subtree in subtrees:
724 for test in subtree.walk():
725 if test in seen:
726 continue
727 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800728
Jon Salz0697cbf2012-07-04 15:14:04 +0800729 if not test.is_leaf():
730 continue
731 if (untested_only and
732 test.get_state().status != TestState.UNTESTED):
733 continue
734 self.tests_to_run.append(test)
735 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800736
Jon Salz0697cbf2012-07-04 15:14:04 +0800737 def reap_completed_tests(self):
738 '''
739 Removes completed tests from the set of active tests.
740
741 Also updates the visible test if it was reaped.
742 '''
743 for t, v in dict(self.invocations).iteritems():
744 if v.is_completed():
Jon Salz1acc8742012-07-17 17:45:55 +0800745 new_state = t.update_state(**v.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800746 del self.invocations[t]
747
Jon Salz1acc8742012-07-17 17:45:55 +0800748 if new_state.iterations_left and new_state.status == TestState.PASSED:
749 # Play it again, Sam!
750 self._run_test(t)
751
Jon Salz0697cbf2012-07-04 15:14:04 +0800752 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800753 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800754 self.set_visible_test(None)
755 # Make the first running test, if any, the visible test
756 for t in self.test_list.walk():
757 if t in self.invocations:
758 self.set_visible_test(t)
759 break
760
Jon Salz85a39882012-07-05 16:45:04 +0800761 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800762 '''
763 Kills and waits for all active tests.
764
Jon Salz85a39882012-07-05 16:45:04 +0800765 Args:
766 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800767 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800768 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800769 '''
770 self.reap_completed_tests()
771 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800772 if root and not test.has_ancestor(root):
773 continue
774
Jon Salz0697cbf2012-07-04 15:14:04 +0800775 factory.console.info('Killing active test %s...' % test.path)
776 invoc.abort_and_join()
777 factory.console.info('Killed %s' % test.path)
Jon Salz1acc8742012-07-17 17:45:55 +0800778 test.update_state(**invoc.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800779 del self.invocations[test]
Jon Salz1acc8742012-07-17 17:45:55 +0800780
Jon Salz0697cbf2012-07-04 15:14:04 +0800781 if not abort:
782 test.update_state(status=TestState.UNTESTED)
783 self.reap_completed_tests()
784
Jon Salz85a39882012-07-05 16:45:04 +0800785 def stop(self, root=None, fail=False):
786 self.kill_active_tests(fail, root)
787 # Remove any tests in the run queue under the root.
788 self.tests_to_run = deque([x for x in self.tests_to_run
789 if root and not x.has_ancestor(root)])
790 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800791
792 def abort_active_tests(self):
793 self.kill_active_tests(True)
794
795 def main(self):
796 try:
797 self.init()
798 self.event_log.Log('goofy_init',
799 success=True)
800 except:
801 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800802 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800803 self.event_log.Log('goofy_init',
804 success=False,
805 trace=traceback.format_exc())
806 except: # pylint: disable=W0702
807 pass
808 raise
809
810 self.run()
811
812 def update_system_info(self):
813 '''Updates system info.'''
814 system_info = system.SystemInfo()
815 self.state_instance.set_shared_data('system_info', system_info.__dict__)
816 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
817 system_info=system_info.__dict__))
818 logging.info('System info: %r', system_info.__dict__)
819
Jon Salz5c344f62012-07-13 14:31:16 +0800820 def update_factory(self, auto_run_on_restart=False):
821 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800822 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +0800823 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800824
Jon Salz5c344f62012-07-13 14:31:16 +0800825 def pre_update_hook():
826 if auto_run_on_restart:
827 self.state_instance.set_shared_data('tests_after_shutdown',
828 FORCE_AUTO_RUN)
829 self.state_instance.close()
830
Jon Salz0697cbf2012-07-04 15:14:04 +0800831 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800832 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800833 self.env.shutdown('reboot')
834 except: # pylint: disable=W0702
835 factory.console.exception('Unable to update')
836
837 def init(self, args=None, env=None):
838 '''Initializes Goofy.
839
840 Args:
841 args: A list of command-line arguments. Uses sys.argv if
842 args is None.
843 env: An Environment instance to use (or None to choose
844 FakeChrootEnvironment or DUTEnvironment as appropriate).
845 '''
846 parser = OptionParser()
847 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +0800848 action='store_true',
849 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +0800850 parser.add_option('--print_test_list', dest='print_test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800851 metavar='FILE',
852 help='Read and print test list FILE, and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +0800853 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +0800854 action='store_true',
855 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +0800856 parser.add_option('--ui', dest='ui', type='choice',
Jon Salz8fa8e832012-07-13 19:04:09 +0800857 choices=['none', 'gtk', 'chrome'],
858 default=('chrome' if utils.in_chroot() else 'gtk'),
859 help='UI to use')
Jon Salz0697cbf2012-07-04 15:14:04 +0800860 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
Jon Salz8fa8e832012-07-13 19:04:09 +0800861 type='int', default=1,
862 help=('Factor by which to scale UI '
863 '(Chrome UI only)'))
Jon Salz0697cbf2012-07-04 15:14:04 +0800864 parser.add_option('--test_list', dest='test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800865 metavar='FILE',
866 help='Use FILE as test list')
Jon Salz0697cbf2012-07-04 15:14:04 +0800867 (self.options, self.args) = parser.parse_args(args)
868
Jon Salz46b89562012-07-05 11:49:22 +0800869 # Make sure factory directories exist.
870 factory.get_log_root()
871 factory.get_state_root()
872 factory.get_test_data_root()
873
Jon Salz0697cbf2012-07-04 15:14:04 +0800874 global _inited_logging # pylint: disable=W0603
875 if not _inited_logging:
876 factory.init_logging('goofy', verbose=self.options.verbose)
877 _inited_logging = True
Jon Salz8fa8e832012-07-13 19:04:09 +0800878
Jon Salzee85d522012-07-17 14:34:46 +0800879 event_log.IncrementBootSequence()
Jon Salz0697cbf2012-07-04 15:14:04 +0800880 self.event_log = EventLog('goofy')
881
882 if (not suppress_chroot_warning and
883 factory.in_chroot() and
884 self.options.ui == 'gtk' and
885 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
886 # That's not going to work! Tell the user how to run
887 # this way.
888 logging.warn(GOOFY_IN_CHROOT_WARNING)
889 time.sleep(1)
890
891 if env:
892 self.env = env
893 elif factory.in_chroot():
894 self.env = test_environment.FakeChrootEnvironment()
895 logging.warn(
896 'Using chroot environment: will not actually run autotests')
897 else:
898 self.env = test_environment.DUTEnvironment()
899 self.env.goofy = self
900
901 if self.options.restart:
902 state.clear_state()
903
904 if self.options.print_test_list:
905 print (factory.read_test_list(
906 self.options.print_test_list,
907 test_classes=dict(test_steps.__dict__)).
908 __repr__(recursive=True))
909 return
910
911 if self.options.ui_scale_factor != 1 and utils.in_qemu():
912 logging.warn(
913 'In QEMU; ignoring ui_scale_factor argument')
914 self.options.ui_scale_factor = 1
915
916 logging.info('Started')
917
918 self.start_state_server()
919 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
920 self.state_instance.set_shared_data('ui_scale_factor',
921 self.options.ui_scale_factor)
922 self.last_shutdown_time = (
923 self.state_instance.get_shared_data('shutdown_time', optional=True))
924 self.state_instance.del_shared_data('shutdown_time', optional=True)
925
926 if not self.options.test_list:
927 self.options.test_list = find_test_list()
928 if not self.options.test_list:
929 logging.error('No test list. Aborting.')
930 sys.exit(1)
931 logging.info('Using test list %s', self.options.test_list)
932
933 self.test_list = factory.read_test_list(
934 self.options.test_list,
935 self.state_instance,
936 test_classes=dict(test_steps.__dict__))
937 if not self.state_instance.has_shared_data('ui_lang'):
938 self.state_instance.set_shared_data('ui_lang',
939 self.test_list.options.ui_lang)
940 self.state_instance.set_shared_data(
941 'test_list_options',
942 self.test_list.options.__dict__)
943 self.state_instance.test_list = self.test_list
944
Jon Salz8fa8e832012-07-13 19:04:09 +0800945 if self.test_list.options.time_sanitizer:
946 self.time_sanitizer = time_sanitizer.TimeSanitizer(
947 base_time=time_sanitizer.GetBaseTimeFromFile(
948 # lsb-factory is written by the factory install shim during
949 # installation, so it should have a good time obtained from
950 # the mini-Omaha server.
951 '/usr/local/etc/lsb-factory'))
952 self.time_sanitizer.RunOnce()
953
Jon Salz0697cbf2012-07-04 15:14:04 +0800954 self.init_states()
955 self.start_event_server()
956 self.connection_manager = self.env.create_connection_manager(
957 self.test_list.options.wlans)
958 # Note that we create a log watcher even if
959 # sync_event_log_period_secs isn't set (no background
960 # syncing), since we may use it to flush event logs as well.
961 self.log_watcher = EventLogWatcher(
962 self.test_list.options.sync_event_log_period_secs,
Jon Salz16d10542012-07-23 12:18:45 +0800963 handle_event_logs_callback=self.handle_event_logs)
Jon Salz0697cbf2012-07-04 15:14:04 +0800964 if self.test_list.options.sync_event_log_period_secs:
965 self.log_watcher.StartWatchThread()
966
967 self.update_system_info()
968
Vic Yang4953fc12012-07-26 16:19:53 +0800969 assert ((self.test_list.options.min_charge_pct is None) ==
970 (self.test_list.options.max_charge_pct is None))
971 if (self.test_list.options.min_charge_pct and
972 self.test_list.options.max_charge_pct):
973 self.charge_manager = ChargeManager(self.test_list.options.min_charge_pct,
974 self.test_list.options.max_charge_pct)
975
Jon Salz0697cbf2012-07-04 15:14:04 +0800976 os.environ['CROS_FACTORY'] = '1'
977 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
978
979 # Set CROS_UI since some behaviors in ui.py depend on the
980 # particular UI in use. TODO(jsalz): Remove this (and all
981 # places it is used) when the GTK UI is removed.
982 os.environ['CROS_UI'] = self.options.ui
983
984 if self.options.ui == 'chrome':
985 self.env.launch_chrome()
986 logging.info('Waiting for a web socket connection')
987 self.web_socket_manager.wait()
988
989 # Wait for the test widget size to be set; this is done in
990 # an asynchronous RPC so there is a small chance that the
991 # web socket might be opened first.
992 for _ in range(100): # 10 s
993 try:
994 if self.state_instance.get_shared_data('test_widget_size'):
995 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800996 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800997 pass # Retry
998 time.sleep(0.1) # 100 ms
999 else:
1000 logging.warn('Never received test_widget_size from UI')
1001 elif self.options.ui == 'gtk':
1002 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001003
Jon Salz0697cbf2012-07-04 15:14:04 +08001004 def state_change_callback(test, test_state):
1005 self.event_client.post_event(
1006 Event(Event.Type.STATE_CHANGE,
1007 path=test.path, state=test_state))
1008 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +08001009
Jon Salza6711d72012-07-18 14:33:03 +08001010 for handler in self.on_ui_startup:
1011 handler()
1012
1013 self.prespawner = Prespawner()
1014 self.prespawner.start()
1015
Jon Salz0697cbf2012-07-04 15:14:04 +08001016 try:
1017 tests_after_shutdown = self.state_instance.get_shared_data(
1018 'tests_after_shutdown')
1019 except KeyError:
1020 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +08001021
Jon Salz5c344f62012-07-13 14:31:16 +08001022 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
1023 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +08001024 logging.info('Resuming tests after shutdown: %s',
1025 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +08001026 self.tests_to_run.extend(
1027 self.test_list.lookup_path(t) for t in tests_after_shutdown)
1028 self.run_queue.put(self.run_next_test)
1029 else:
Jon Salz5c344f62012-07-13 14:31:16 +08001030 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +08001031 self.run_queue.put(
1032 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +08001033 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001034
Jon Salz0697cbf2012-07-04 15:14:04 +08001035 def run(self):
1036 '''Runs Goofy.'''
1037 # Process events forever.
1038 while self.run_once(True):
1039 pass
Jon Salz73e0fd02012-04-04 11:46:38 +08001040
Jon Salz0697cbf2012-07-04 15:14:04 +08001041 def run_once(self, block=False):
1042 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001043
Jon Salz0697cbf2012-07-04 15:14:04 +08001044 Args:
1045 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +08001046
Jon Salz0697cbf2012-07-04 15:14:04 +08001047 Returns:
1048 True to keep going or False to shut down.
1049 '''
1050 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +08001051 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +08001052 # Nothing on the run queue.
1053 self._run_queue_idle()
1054 if block:
1055 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +08001056 try:
1057 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
1058 except Queue.Empty:
1059 # Keep going (calling _run_queue_idle() again at the top of
1060 # the loop)
1061 continue
Jon Salz0697cbf2012-07-04 15:14:04 +08001062 # ...and grab anything else that showed up at the same
1063 # time.
1064 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +08001065 else:
1066 break
Jon Salz51528e12012-07-02 18:54:45 +08001067
Jon Salz0697cbf2012-07-04 15:14:04 +08001068 for event in events:
1069 if not event:
1070 # Shutdown request.
1071 self.run_queue.task_done()
1072 return False
Jon Salz51528e12012-07-02 18:54:45 +08001073
Jon Salz0697cbf2012-07-04 15:14:04 +08001074 try:
1075 event()
Jon Salz85a39882012-07-05 16:45:04 +08001076 except: # pylint: disable=W0702
1077 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001078 self.record_exception(traceback.format_exception_only(
1079 *sys.exc_info()[:2]))
1080 # But keep going
1081 finally:
1082 self.run_queue.task_done()
1083 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001084
Jon Salz8fa8e832012-07-13 19:04:09 +08001085 def sync_time_in_background(self):
1086 '''Attempts to sync time with the shopfloor server.'''
1087 if ((not self.test_list.options.sync_time_period_secs) or
1088 (not self.time_sanitizer) or
1089 self.time_synced or
1090 factory.in_chroot()):
1091 # Not enabled or already succeeded.
1092 return
1093
1094 now = time.time()
1095 if self.last_sync_time and (
1096 now - self.last_sync_time <
1097 self.test_list.options.sync_time_period_secs):
1098 # Not yet time for another check.
1099 return
1100 self.last_sync_time = now
1101
1102 def target():
1103 try:
1104 self.time_sanitizer.SyncWithShopfloor()
1105 self.time_synced = True
1106 except: # pylint: disable=W0702
1107 # Oh well. Log an error (but no trace)
1108 logging.info(
1109 'Unable to get time from shopfloor server: %s',
1110 utils.FormatExceptionOnly())
1111
1112 thread = threading.Thread(target=target)
1113 thread.daemon = True
1114 thread.start()
1115
Jon Salz0697cbf2012-07-04 15:14:04 +08001116 def _run_queue_idle(self):
Vic Yang4953fc12012-07-26 16:19:53 +08001117 '''Invoked when the run queue has no events.
1118
1119 This method must not raise exception.
1120 '''
Jon Salz0697cbf2012-07-04 15:14:04 +08001121 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001122 self.check_for_updates()
Jon Salz8fa8e832012-07-13 19:04:09 +08001123 self.sync_time_in_background()
Vic Yang4953fc12012-07-26 16:19:53 +08001124 if self.charge_manager:
1125 self.charge_manager.AdjustChargeState()
Jon Salz57717ca2012-04-04 16:47:25 +08001126
Jon Salz16d10542012-07-23 12:18:45 +08001127 def handle_event_logs(self, log_name, chunk):
Jon Salz0697cbf2012-07-04 15:14:04 +08001128 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001129
Jon Salz0697cbf2012-07-04 15:14:04 +08001130 Attempts to upload the event logs to the shopfloor server.
1131 '''
1132 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1133 start_time = time.time()
1134 logging.info('Syncing %s', description)
1135 shopfloor_client = shopfloor.get_instance(
1136 detect=True,
1137 timeout=self.test_list.options.shopfloor_timeout_secs)
1138 shopfloor_client.UploadEvent(log_name, chunk)
1139 logging.info(
1140 'Successfully synced %s in %.03f s',
1141 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001142
Jon Salz0697cbf2012-07-04 15:14:04 +08001143 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1144 root=None):
1145 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001146
Jon Salz0697cbf2012-07-04 15:14:04 +08001147 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001148
Jon Salz0697cbf2012-07-04 15:14:04 +08001149 Args:
1150 starting_at: If provided, only auto-runs tests beginning with
1151 this test.
1152 '''
1153 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001154
Jon Salz0697cbf2012-07-04 15:14:04 +08001155 if starting_at:
1156 # Make sure they passed a test, not a string.
1157 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001158
Jon Salz0697cbf2012-07-04 15:14:04 +08001159 tests_to_reset = []
1160 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001161
Jon Salz0697cbf2012-07-04 15:14:04 +08001162 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001163
Jon Salz0697cbf2012-07-04 15:14:04 +08001164 for test in root.get_top_level_tests():
1165 if starting_at:
1166 if test == starting_at:
1167 # We've found starting_at; do auto-run on all
1168 # subsequent tests.
1169 found_starting_at = True
1170 if not found_starting_at:
1171 # Don't start this guy yet
1172 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001173
Jon Salz0697cbf2012-07-04 15:14:04 +08001174 status = test.get_state().status
1175 if status == TestState.ACTIVE or status in statuses_to_run:
1176 # Reset the test (later; we will need to abort
1177 # all active tests first).
1178 tests_to_reset.append(test)
1179 if status in statuses_to_run:
1180 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001181
Jon Salz0697cbf2012-07-04 15:14:04 +08001182 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001183
Jon Salz0697cbf2012-07-04 15:14:04 +08001184 # Reset all statuses of the tests to run (in case any tests were active;
1185 # we want them to be run again).
1186 for test_to_reset in tests_to_reset:
1187 for test in test_to_reset.walk():
1188 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001189
Jon Salz0697cbf2012-07-04 15:14:04 +08001190 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001191
Jon Salz0697cbf2012-07-04 15:14:04 +08001192 def restart_tests(self, root=None):
1193 '''Restarts all tests.'''
1194 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001195
Jon Salz0697cbf2012-07-04 15:14:04 +08001196 self.abort_active_tests()
1197 for test in root.walk():
1198 test.update_state(status=TestState.UNTESTED)
1199 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001200
Jon Salz0697cbf2012-07-04 15:14:04 +08001201 def auto_run(self, starting_at=None, root=None):
1202 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001203
Jon Salz0697cbf2012-07-04 15:14:04 +08001204 Args:
1205 starting_at: If provide, only auto-runs tests beginning with
1206 this test.
1207 '''
1208 root = root or self.test_list
1209 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1210 starting_at=starting_at,
1211 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001212
Jon Salz0697cbf2012-07-04 15:14:04 +08001213 def re_run_failed(self, root=None):
1214 '''Re-runs failed tests.'''
1215 root = root or self.test_list
1216 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001217
Jon Salz0697cbf2012-07-04 15:14:04 +08001218 def show_review_information(self):
1219 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001220
Jon Salz0697cbf2012-07-04 15:14:04 +08001221 The information screene is rendered by main UI program (ui.py), so in
1222 goofy we only need to kill all active tests, set them as untested, and
1223 clear remaining tests.
1224 '''
1225 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +08001226 self.cancel_pending_tests()
Jon Salz57717ca2012-04-04 16:47:25 +08001227
Jon Salz0697cbf2012-07-04 15:14:04 +08001228 def handle_switch_test(self, event):
1229 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001230
Jon Salz0697cbf2012-07-04 15:14:04 +08001231 @param event: The SWITCH_TEST event.
1232 '''
1233 test = self.test_list.lookup_path(event.path)
1234 if not test:
1235 logging.error('Unknown test %r', event.key)
1236 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001237
Jon Salz0697cbf2012-07-04 15:14:04 +08001238 invoc = self.invocations.get(test)
1239 if invoc and test.backgroundable:
1240 # Already running: just bring to the front if it
1241 # has a UI.
1242 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001243 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001244 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001245
Jon Salz0697cbf2012-07-04 15:14:04 +08001246 self.abort_active_tests()
1247 for t in test.walk():
1248 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001249
Jon Salz0697cbf2012-07-04 15:14:04 +08001250 if self.test_list.options.auto_run_on_keypress:
1251 self.auto_run(starting_at=test)
1252 else:
1253 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001254
Jon Salz0697cbf2012-07-04 15:14:04 +08001255 def wait(self):
1256 '''Waits for all pending invocations.
1257
1258 Useful for testing.
1259 '''
Jon Salz1acc8742012-07-17 17:45:55 +08001260 while self.invocations:
1261 for k, v in self.invocations.iteritems():
1262 logging.info('Waiting for %s to complete...', k)
1263 v.thread.join()
1264 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001265
1266 def check_exceptions(self):
1267 '''Raises an error if any exceptions have occurred in
1268 invocation threads.'''
1269 if self.exceptions:
1270 raise RuntimeError('Exception in invocation thread: %r' %
1271 self.exceptions)
1272
1273 def record_exception(self, msg):
1274 '''Records an exception in an invocation thread.
1275
1276 An exception with the given message will be rethrown when
1277 Goofy is destroyed.'''
1278 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001279
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001280
1281if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001282 Goofy().main()