blob: 9b970179da86d55e9ef47dbb8da7ae00ddc947e2 [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
Hung-Te Linf2f78f72012-02-08 19:27:11 +08002# -*- coding: utf-8 -*-
3#
Jon Salz37eccbd2012-05-25 16:06:52 +08004# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08005# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8'''
9The main factory flow that runs the factory test and finalizes a device.
10'''
11
Jon Salz0405ab52012-03-16 15:26:52 +080012import logging
13import os
Jon Salz73e0fd02012-04-04 11:46:38 +080014import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080015import subprocess
16import sys
Jon Salz0405ab52012-03-16 15:26:52 +080017import threading
18import time
19import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080020import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080021from collections import deque
22from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080023
Jon Salz0697cbf2012-07-04 15:14:04 +080024import factory_common # pylint: disable=W0611
Jon Salz83591782012-06-26 11:09:58 +080025from cros.factory.goofy.prespawner import Prespawner
26from cros.factory.test import factory
27from cros.factory.test import state
28from cros.factory.test.factory import TestState
29from cros.factory.goofy import updater
Jon Salz51528e12012-07-02 18:54:45 +080030from cros.factory.goofy import test_steps
31from cros.factory.goofy.event_log_watcher import EventLogWatcher
32from cros.factory.test import shopfloor
Jon Salz83591782012-06-26 11:09:58 +080033from cros.factory.test import utils
34from cros.factory.test.event import Event
35from cros.factory.test.event import EventClient
36from cros.factory.test.event import EventServer
37from cros.factory.event_log import EventLog
38from cros.factory.goofy.invocation import TestInvocation
39from cros.factory.goofy import system
40from cros.factory.goofy import test_environment
Jon Salz8fa8e832012-07-13 19:04:09 +080041from cros.factory.goofy import time_sanitizer
Jon Salz83591782012-06-26 11:09:58 +080042from cros.factory.goofy.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080043
44
Jon Salz2f757d42012-06-27 17:06:42 +080045DEFAULT_TEST_LISTS_DIR = os.path.join(factory.FACTORY_PATH, 'test_lists')
46CUSTOM_DIR = os.path.join(factory.FACTORY_PATH, 'custom')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080047HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
48
Jon Salz8796e362012-05-24 11:39:09 +080049# File that suppresses reboot if present (e.g., for development).
50NO_REBOOT_FILE = '/var/log/factory.noreboot'
51
Jon Salz5c344f62012-07-13 14:31:16 +080052# Value for tests_after_shutdown that forces auto-run (e.g., after
53# a factory update, when the available set of tests might change).
54FORCE_AUTO_RUN = 'force_auto_run'
55
cychiang21886742012-07-05 15:16:32 +080056RUN_QUEUE_TIMEOUT_SECS = 10
57
Jon Salz758e6cc2012-04-03 15:47:07 +080058GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
59You are running Goofy inside the chroot. Autotests are not supported.
60
61To use Goofy in the chroot, first install an Xvnc server:
62
Jon Salz0697cbf2012-07-04 15:14:04 +080063 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080064
65...and then start a VNC X server outside the chroot:
66
Jon Salz0697cbf2012-07-04 15:14:04 +080067 vncserver :10 &
68 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080069
70...and run Goofy as follows:
71
Jon Salz0697cbf2012-07-04 15:14:04 +080072 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080073''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080074suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080075
76def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080077 '''
78 Returns the HWID config tag, or an empty string if none can be found.
79 '''
80 if 'CROS_HWID' in os.environ:
81 return os.environ['CROS_HWID']
82 if os.path.exists(HWID_CFG_PATH):
83 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
84 return hwid_cfg_handle.read().strip()
85 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080086
87
88def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080089 '''
90 Returns the path to the active test list, based on the HWID config tag.
91 '''
92 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080093
Jon Salz0697cbf2012-07-04 15:14:04 +080094 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080095
Jon Salz0697cbf2012-07-04 15:14:04 +080096 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
97 search_files = ['test_list', 'test_list.all']
98 if hwid_cfg:
99 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800100
Jon Salz0697cbf2012-07-04 15:14:04 +0800101 for d in search_dirs:
102 for f in search_files:
103 test_list = os.path.join(d, f)
104 if os.path.exists(test_list):
105 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800106
Jon Salz0697cbf2012-07-04 15:14:04 +0800107 logging.warn('Cannot find test lists named any of %s in any of %s',
108 search_files, search_dirs)
109 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800110
Jon Salz73e0fd02012-04-04 11:46:38 +0800111_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800112
113class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800114 '''
115 The main factory flow.
116
117 Note that all methods in this class must be invoked from the main
118 (event) thread. Other threads, such as callbacks and TestInvocation
119 methods, should instead post events on the run queue.
120
121 TODO: Unit tests. (chrome-os-partner:7409)
122
123 Properties:
124 uuid: A unique UUID for this invocation of Goofy.
125 state_instance: An instance of FactoryState.
126 state_server: The FactoryState XML/RPC server.
127 state_server_thread: A thread running state_server.
128 event_server: The EventServer socket server.
129 event_server_thread: A thread running event_server.
130 event_client: A client to the event server.
131 connection_manager: The connection_manager object.
132 network_enabled: Whether the connection_manager is currently
133 enabling connections.
134 ui_process: The factory ui process object.
135 run_queue: A queue of callbacks to invoke from the main thread.
136 invocations: A map from FactoryTest objects to the corresponding
137 TestInvocations objects representing active tests.
138 tests_to_run: A deque of tests that should be run when the current
139 test(s) complete.
140 options: Command-line options.
141 args: Command-line args.
142 test_list: The test list.
143 event_handlers: Map of Event.Type to the method used to handle that
144 event. If the method has an 'event' argument, the event is passed
145 to the handler.
146 exceptions: Exceptions encountered in invocation threads.
147 '''
148 def __init__(self):
149 self.uuid = str(uuid.uuid4())
150 self.state_instance = None
151 self.state_server = None
152 self.state_server_thread = None
153 self.event_server = None
154 self.event_server_thread = None
155 self.event_client = None
156 self.connection_manager = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800157 self.time_sanitizer = None
158 self.time_synced = False
Jon Salz0697cbf2012-07-04 15:14:04 +0800159 self.log_watcher = None
160 self.network_enabled = True
161 self.event_log = None
162 self.prespawner = None
163 self.ui_process = None
164 self.run_queue = Queue.Queue()
165 self.invocations = {}
166 self.tests_to_run = deque()
167 self.visible_test = None
168 self.chrome = None
169
170 self.options = None
171 self.args = None
172 self.test_list = None
173 self.on_ui_startup = []
174 self.env = None
175 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800176 self.last_update_check = None
Jon Salz8fa8e832012-07-13 19:04:09 +0800177 self.last_sync_time = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800178
Jon Salz85a39882012-07-05 16:45:04 +0800179 def test_or_root(event, parent_or_group=True):
180 '''Returns the test affected by a particular event.
181
182 Args:
183 event: The event containing an optional 'path' attribute.
184 parent_on_group: If True, returns the top-level parent for a test (the
185 root node of the tests that need to be run together if the given test
186 path is to be run).
187 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800188 try:
189 path = event.path
190 except AttributeError:
191 path = None
192
193 if path:
Jon Salz85a39882012-07-05 16:45:04 +0800194 test = self.test_list.lookup_path(path)
195 if parent_or_group:
196 test = test.get_top_level_parent_or_group()
197 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800198 else:
199 return self.test_list
200
201 self.event_handlers = {
202 Event.Type.SWITCH_TEST: self.handle_switch_test,
203 Event.Type.SHOW_NEXT_ACTIVE_TEST:
204 lambda event: self.show_next_active_test(),
205 Event.Type.RESTART_TESTS:
206 lambda event: self.restart_tests(root=test_or_root(event)),
207 Event.Type.AUTO_RUN:
208 lambda event: self.auto_run(root=test_or_root(event)),
209 Event.Type.RE_RUN_FAILED:
210 lambda event: self.re_run_failed(root=test_or_root(event)),
211 Event.Type.RUN_TESTS_WITH_STATUS:
212 lambda event: self.run_tests_with_status(
213 event.status,
214 root=test_or_root(event)),
215 Event.Type.REVIEW:
216 lambda event: self.show_review_information(),
217 Event.Type.UPDATE_SYSTEM_INFO:
218 lambda event: self.update_system_info(),
219 Event.Type.UPDATE_FACTORY:
220 lambda event: self.update_factory(),
221 Event.Type.STOP:
Jon Salz85a39882012-07-05 16:45:04 +0800222 lambda event: self.stop(root=test_or_root(event, False),
223 fail=getattr(event, 'fail', False)),
Jon Salz36fbbb52012-07-05 13:45:06 +0800224 Event.Type.SET_VISIBLE_TEST:
225 lambda event: self.set_visible_test(
226 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800227 }
228
229 self.exceptions = []
230 self.web_socket_manager = None
231
232 def destroy(self):
233 if self.chrome:
234 self.chrome.kill()
235 self.chrome = None
236 if self.ui_process:
237 utils.kill_process_tree(self.ui_process, 'ui')
238 self.ui_process = None
239 if self.web_socket_manager:
240 logging.info('Stopping web sockets')
241 self.web_socket_manager.close()
242 self.web_socket_manager = None
243 if self.state_server_thread:
244 logging.info('Stopping state server')
245 self.state_server.shutdown()
246 self.state_server_thread.join()
247 self.state_server.server_close()
248 self.state_server_thread = None
249 if self.state_instance:
250 self.state_instance.close()
251 if self.event_server_thread:
252 logging.info('Stopping event server')
253 self.event_server.shutdown() # pylint: disable=E1101
254 self.event_server_thread.join()
255 self.event_server.server_close()
256 self.event_server_thread = None
257 if self.log_watcher:
258 if self.log_watcher.IsThreadStarted():
259 self.log_watcher.StopWatchThread()
260 self.log_watcher = None
261 if self.prespawner:
262 logging.info('Stopping prespawner')
263 self.prespawner.stop()
264 self.prespawner = None
265 if self.event_client:
266 logging.info('Closing event client')
267 self.event_client.close()
268 self.event_client = None
269 if self.event_log:
270 self.event_log.Close()
271 self.event_log = None
272 self.check_exceptions()
273 logging.info('Done destroying Goofy')
274
275 def start_state_server(self):
276 self.state_instance, self.state_server = (
277 state.create_server(bind_address='0.0.0.0'))
278 logging.info('Starting state server')
279 self.state_server_thread = threading.Thread(
280 target=self.state_server.serve_forever,
281 name='StateServer')
282 self.state_server_thread.start()
283
284 def start_event_server(self):
285 self.event_server = EventServer()
286 logging.info('Starting factory event server')
287 self.event_server_thread = threading.Thread(
288 target=self.event_server.serve_forever,
289 name='EventServer') # pylint: disable=E1101
290 self.event_server_thread.start()
291
292 self.event_client = EventClient(
293 callback=self.handle_event, event_loop=self.run_queue)
294
295 self.web_socket_manager = WebSocketManager(self.uuid)
296 self.state_server.add_handler("/event",
297 self.web_socket_manager.handle_web_socket)
298
299 def start_ui(self):
300 ui_proc_args = [
301 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
302 self.options.test_list]
303 if self.options.verbose:
304 ui_proc_args.append('-v')
305 logging.info('Starting ui %s', ui_proc_args)
306 self.ui_process = subprocess.Popen(ui_proc_args)
307 logging.info('Waiting for UI to come up...')
308 self.event_client.wait(
309 lambda event: event.type == Event.Type.UI_READY)
310 logging.info('UI has started')
311
312 def set_visible_test(self, test):
313 if self.visible_test == test:
314 return
315
316 if test:
317 test.update_state(visible=True)
318 if self.visible_test:
319 self.visible_test.update_state(visible=False)
320 self.visible_test = test
321
322 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800323 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800324 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800325
Jon Salz0697cbf2012-07-04 15:14:04 +0800326 @param test: The ShutdownStep.
327 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800328 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800329 test_state = test.update_state(increment_shutdown_count=1)
330 logging.info('Detected shutdown (%d of %d)',
331 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800332
Jon Salz0697cbf2012-07-04 15:14:04 +0800333 def log_and_update_state(status, error_msg, **kw):
334 self.event_log.Log('rebooted',
335 status=status, error_msg=error_msg, **kw)
336 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800337
Jon Salz0697cbf2012-07-04 15:14:04 +0800338 if not self.last_shutdown_time:
339 log_and_update_state(status=TestState.FAILED,
340 error_msg='Unable to read shutdown_time')
341 return
Jon Salz258a40c2012-04-19 12:34:01 +0800342
Jon Salz0697cbf2012-07-04 15:14:04 +0800343 now = time.time()
344 logging.info('%.03f s passed since reboot',
345 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800346
Jon Salz0697cbf2012-07-04 15:14:04 +0800347 if self.last_shutdown_time > now:
348 test.update_state(status=TestState.FAILED,
349 error_msg='Time moved backward during reboot')
350 elif (isinstance(test, factory.RebootStep) and
351 self.test_list.options.max_reboot_time_secs and
352 (now - self.last_shutdown_time >
353 self.test_list.options.max_reboot_time_secs)):
354 # A reboot took too long; fail. (We don't check this for
355 # HaltSteps, because the machine could be halted for a
356 # very long time, and even unplugged with battery backup,
357 # thus hosing the clock.)
358 log_and_update_state(
359 status=TestState.FAILED,
360 error_msg=('More than %d s elapsed during reboot '
361 '(%.03f s, from %s to %s)' % (
362 self.test_list.options.max_reboot_time_secs,
363 now - self.last_shutdown_time,
364 utils.TimeString(self.last_shutdown_time),
365 utils.TimeString(now))),
366 duration=(now-self.last_shutdown_time))
367 elif test_state.shutdown_count == test.iterations:
368 # Good!
369 log_and_update_state(status=TestState.PASSED,
370 duration=(now - self.last_shutdown_time),
371 error_msg='')
372 elif test_state.shutdown_count > test.iterations:
373 # Shut down too many times
374 log_and_update_state(status=TestState.FAILED,
375 error_msg='Too many shutdowns')
376 elif utils.are_shift_keys_depressed():
377 logging.info('Shift keys are depressed; cancelling restarts')
378 # Abort shutdown
379 log_and_update_state(
380 status=TestState.FAILED,
381 error_msg='Shutdown aborted with double shift keys')
Jon Salza6711d72012-07-18 14:33:03 +0800382 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800383 else:
384 def handler():
385 if self._prompt_cancel_shutdown(
386 test, test_state.shutdown_count + 1):
Jon Salza6711d72012-07-18 14:33:03 +0800387 factory.console.info('Shutdown aborted by operator')
Jon Salz0697cbf2012-07-04 15:14:04 +0800388 log_and_update_state(
389 status=TestState.FAILED,
390 error_msg='Shutdown aborted by operator')
Jon Salza6711d72012-07-18 14:33:03 +0800391 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800392 return
Jon Salz0405ab52012-03-16 15:26:52 +0800393
Jon Salz0697cbf2012-07-04 15:14:04 +0800394 # Time to shutdown again
395 log_and_update_state(
396 status=TestState.ACTIVE,
397 error_msg='',
398 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800399
Jon Salz0697cbf2012-07-04 15:14:04 +0800400 self.event_log.Log('shutdown', operation='reboot')
401 self.state_instance.set_shared_data('shutdown_time',
402 time.time())
403 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800404
Jon Salz0697cbf2012-07-04 15:14:04 +0800405 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800406
Jon Salz0697cbf2012-07-04 15:14:04 +0800407 def _prompt_cancel_shutdown(self, test, iteration):
408 if self.options.ui != 'chrome':
409 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800410
Jon Salz0697cbf2012-07-04 15:14:04 +0800411 pending_shutdown_data = {
412 'delay_secs': test.delay_secs,
413 'time': time.time() + test.delay_secs,
414 'operation': test.operation,
415 'iteration': iteration,
416 'iterations': test.iterations,
417 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800418
Jon Salz0697cbf2012-07-04 15:14:04 +0800419 # Create a new (threaded) event client since we
420 # don't want to use the event loop for this.
421 with EventClient() as event_client:
422 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
423 **pending_shutdown_data))
424 aborted = event_client.wait(
425 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
426 timeout=test.delay_secs) is not None
427 if aborted:
428 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
429 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800430
Jon Salz0697cbf2012-07-04 15:14:04 +0800431 def init_states(self):
432 '''
433 Initializes all states on startup.
434 '''
435 for test in self.test_list.get_all_tests():
436 # Make sure the state server knows about all the tests,
437 # defaulting to an untested state.
438 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800439
Jon Salz0697cbf2012-07-04 15:14:04 +0800440 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800441
Jon Salz0697cbf2012-07-04 15:14:04 +0800442 # Any 'active' tests should be marked as failed now.
443 for test in self.test_list.walk():
Jon Salza6711d72012-07-18 14:33:03 +0800444 if not test.is_leaf():
445 # Don't bother with parents; they will be updated when their
446 # children are updated.
447 continue
448
Jon Salz0697cbf2012-07-04 15:14:04 +0800449 test_state = test.get_state()
450 if test_state.status != TestState.ACTIVE:
451 continue
452 if isinstance(test, factory.ShutdownStep):
453 # Shutdown while the test was active - that's good.
454 self.handle_shutdown_complete(test, test_state)
455 else:
456 # Unexpected shutdown. Grab /var/log/messages for context.
457 if var_log_messages is None:
458 try:
459 var_log_messages = (
460 utils.var_log_messages_before_reboot())
461 # Write it to the log, to make it easier to
462 # correlate with /var/log/messages.
463 logging.info(
464 'Unexpected shutdown. '
465 'Tail of /var/log/messages before last reboot:\n'
466 '%s', ('\n'.join(
467 ' ' + x for x in var_log_messages)))
468 except: # pylint: disable=W0702
469 logging.exception('Unable to grok /var/log/messages')
470 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800471
Jon Salz0697cbf2012-07-04 15:14:04 +0800472 error_msg = 'Unexpected shutdown while test was running'
473 self.event_log.Log('end_test',
474 path=test.path,
475 status=TestState.FAILED,
476 invocation=test.get_state().invocation,
477 error_msg=error_msg,
478 var_log_messages='\n'.join(var_log_messages))
479 test.update_state(
480 status=TestState.FAILED,
481 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800482
Jon Salz69806bb2012-07-20 18:05:02 +0800483 factory.console.info('Unexpected shutdown while test %s '
484 'running; cancelling any pending tests',
485 test.path)
486 self.state_instance.set_shared_data('tests_after_shutdown', [])
487
Jon Salz0697cbf2012-07-04 15:14:04 +0800488 def show_next_active_test(self):
489 '''
490 Rotates to the next visible active test.
491 '''
492 self.reap_completed_tests()
493 active_tests = [
494 t for t in self.test_list.walk()
495 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
496 if not active_tests:
497 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800498
Jon Salz0697cbf2012-07-04 15:14:04 +0800499 try:
500 next_test = active_tests[
501 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
502 except ValueError: # visible_test not present in active_tests
503 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800504
Jon Salz0697cbf2012-07-04 15:14:04 +0800505 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800506
Jon Salz0697cbf2012-07-04 15:14:04 +0800507 def handle_event(self, event):
508 '''
509 Handles an event from the event server.
510 '''
511 handler = self.event_handlers.get(event.type)
512 if handler:
513 handler(event)
514 else:
515 # We don't register handlers for all event types - just ignore
516 # this event.
517 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800518
Jon Salz0697cbf2012-07-04 15:14:04 +0800519 def run_next_test(self):
520 '''
521 Runs the next eligible test (or tests) in self.tests_to_run.
522 '''
523 self.reap_completed_tests()
524 while self.tests_to_run:
525 logging.debug('Tests to run: %s',
526 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800527
Jon Salz0697cbf2012-07-04 15:14:04 +0800528 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800529
Jon Salz0697cbf2012-07-04 15:14:04 +0800530 if test in self.invocations:
531 logging.info('Next test %s is already running', test.path)
532 self.tests_to_run.popleft()
533 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800534
Jon Salz304a75d2012-07-06 11:14:15 +0800535 for i in test.require_run:
536 for j in i.walk():
537 if j.get_state().status == TestState.ACTIVE:
538 logging.info('Waiting for active test %s to complete '
539 'before running %s', j.path, test.path)
540 return
541
Jon Salz0697cbf2012-07-04 15:14:04 +0800542 if self.invocations and not (test.backgroundable and all(
543 [x.backgroundable for x in self.invocations])):
544 logging.debug('Waiting for non-backgroundable tests to '
545 'complete before running %s', test.path)
546 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800547
Jon Salz0697cbf2012-07-04 15:14:04 +0800548 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800549
Jon Salz304a75d2012-07-06 11:14:15 +0800550 untested = set()
551 for i in test.require_run:
552 for j in i.walk():
553 if j == test:
554 # We've hit this test itself; stop checking
555 break
556 if j.get_state().status == TestState.UNTESTED:
557 # Found an untested test; move on to the next
558 # element in require_run.
559 untested.add(j)
560 break
561
562 if untested:
563 untested_paths = ', '.join(sorted([x.path for x in untested]))
564 if self.state_instance.get_shared_data('engineering_mode',
565 optional=True):
566 # In engineering mode, we'll let it go.
567 factory.console.warn('In engineering mode; running '
568 '%s even though required tests '
569 '[%s] have not completed',
570 test.path, untested_paths)
571 else:
572 # Not in engineering mode; mark it failed.
573 error_msg = ('Required tests [%s] have not been run yet'
574 % untested_paths)
575 factory.console.error('Not running %s: %s',
576 test.path, error_msg)
577 test.update_state(status=TestState.FAILED,
578 error_msg=error_msg)
579 continue
580
Jon Salz0697cbf2012-07-04 15:14:04 +0800581 if isinstance(test, factory.ShutdownStep):
582 if os.path.exists(NO_REBOOT_FILE):
583 test.update_state(
584 status=TestState.FAILED, increment_count=1,
585 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800586 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800587 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800588
Jon Salz0697cbf2012-07-04 15:14:04 +0800589 test.update_state(status=TestState.ACTIVE, increment_count=1,
590 error_msg='', shutdown_count=0)
591 if self._prompt_cancel_shutdown(test, 1):
592 self.event_log.Log('reboot_cancelled')
593 test.update_state(
594 status=TestState.FAILED, increment_count=1,
595 error_msg='Shutdown aborted by operator',
596 shutdown_count=0)
597 return
Jon Salz2f757d42012-06-27 17:06:42 +0800598
Jon Salz0697cbf2012-07-04 15:14:04 +0800599 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800600 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800601 'tests_after_shutdown',
602 [t.path for t in self.tests_to_run])
603 # Save shutdown time
604 self.state_instance.set_shared_data('shutdown_time',
605 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800606
Jon Salz0697cbf2012-07-04 15:14:04 +0800607 with self.env.lock:
608 self.event_log.Log('shutdown', operation=test.operation)
609 shutdown_result = self.env.shutdown(test.operation)
610 if shutdown_result:
611 # That's all, folks!
612 self.run_queue.put(None)
613 return
614 else:
615 # Just pass (e.g., in the chroot).
616 test.update_state(status=TestState.PASSED)
617 self.state_instance.set_shared_data(
618 'tests_after_shutdown', None)
619 # Send event with no fields to indicate that there is no
620 # longer a pending shutdown.
621 self.event_client.post_event(Event(
622 Event.Type.PENDING_SHUTDOWN))
623 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800624
Jon Salz1acc8742012-07-17 17:45:55 +0800625 self._run_test(test, test.iterations)
626
627 def _run_test(self, test, iterations_left=None):
628 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
629 new_state = test.update_state(
630 status=TestState.ACTIVE, increment_count=1, error_msg='',
631 invocation=invoc.uuid, iterations_left=iterations_left)
632 invoc.count = new_state.count
633
634 self.invocations[test] = invoc
635 if self.visible_test is None and test.has_ui:
636 self.set_visible_test(test)
637 self.check_connection_manager()
638 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800639
Jon Salz0697cbf2012-07-04 15:14:04 +0800640 def check_connection_manager(self):
641 exclusive_tests = [
642 test.path
643 for test in self.invocations
644 if test.is_exclusive(
645 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
646 if exclusive_tests:
647 # Make sure networking is disabled.
648 if self.network_enabled:
649 logging.info('Disabling network, as requested by %s',
650 exclusive_tests)
651 self.connection_manager.DisableNetworking()
652 self.network_enabled = False
653 else:
654 # Make sure networking is enabled.
655 if not self.network_enabled:
656 logging.info('Re-enabling network')
657 self.connection_manager.EnableNetworking()
658 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800659
cychiang21886742012-07-05 15:16:32 +0800660 def check_for_updates(self):
661 '''
662 Schedules an asynchronous check for updates if necessary.
663 '''
664 if not self.test_list.options.update_period_secs:
665 # Not enabled.
666 return
667
668 now = time.time()
669 if self.last_update_check and (
670 now - self.last_update_check <
671 self.test_list.options.update_period_secs):
672 # Not yet time for another check.
673 return
674
675 self.last_update_check = now
676
677 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
678 if reached_shopfloor:
679 new_update_md5sum = md5sum if needs_update else None
680 if system.SystemInfo.update_md5sum != new_update_md5sum:
681 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
682 system.SystemInfo.update_md5sum = new_update_md5sum
683 self.run_queue.put(self.update_system_info)
684
685 updater.CheckForUpdateAsync(
686 handle_check_for_update,
687 self.test_list.options.shopfloor_timeout_secs)
688
Jon Salza6711d72012-07-18 14:33:03 +0800689 def cancel_pending_tests(self):
690 '''Cancels any tests in the run queue.'''
691 self.run_tests([])
692
Jon Salz0697cbf2012-07-04 15:14:04 +0800693 def run_tests(self, subtrees, untested_only=False):
694 '''
695 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800696
Jon Salz0697cbf2012-07-04 15:14:04 +0800697 The tests are run in order unless one fails (then stops).
698 Backgroundable tests are run simultaneously; when a foreground test is
699 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800700
Jon Salz0697cbf2012-07-04 15:14:04 +0800701 @param subtrees: Node or nodes containing tests to run (may either be
702 a single test or a list). Duplicates will be ignored.
703 '''
704 if type(subtrees) != list:
705 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800706
Jon Salz0697cbf2012-07-04 15:14:04 +0800707 # Nodes we've seen so far, to avoid duplicates.
708 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800709
Jon Salz0697cbf2012-07-04 15:14:04 +0800710 self.tests_to_run = deque()
711 for subtree in subtrees:
712 for test in subtree.walk():
713 if test in seen:
714 continue
715 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800716
Jon Salz0697cbf2012-07-04 15:14:04 +0800717 if not test.is_leaf():
718 continue
719 if (untested_only and
720 test.get_state().status != TestState.UNTESTED):
721 continue
722 self.tests_to_run.append(test)
723 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800724
Jon Salz0697cbf2012-07-04 15:14:04 +0800725 def reap_completed_tests(self):
726 '''
727 Removes completed tests from the set of active tests.
728
729 Also updates the visible test if it was reaped.
730 '''
731 for t, v in dict(self.invocations).iteritems():
732 if v.is_completed():
Jon Salz1acc8742012-07-17 17:45:55 +0800733 new_state = t.update_state(**v.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800734 del self.invocations[t]
735
Jon Salz1acc8742012-07-17 17:45:55 +0800736 if new_state.iterations_left and new_state.status == TestState.PASSED:
737 # Play it again, Sam!
738 self._run_test(t)
739
Jon Salz0697cbf2012-07-04 15:14:04 +0800740 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800741 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800742 self.set_visible_test(None)
743 # Make the first running test, if any, the visible test
744 for t in self.test_list.walk():
745 if t in self.invocations:
746 self.set_visible_test(t)
747 break
748
Jon Salz85a39882012-07-05 16:45:04 +0800749 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800750 '''
751 Kills and waits for all active tests.
752
Jon Salz85a39882012-07-05 16:45:04 +0800753 Args:
754 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800755 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800756 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800757 '''
758 self.reap_completed_tests()
759 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800760 if root and not test.has_ancestor(root):
761 continue
762
Jon Salz0697cbf2012-07-04 15:14:04 +0800763 factory.console.info('Killing active test %s...' % test.path)
764 invoc.abort_and_join()
765 factory.console.info('Killed %s' % test.path)
Jon Salz1acc8742012-07-17 17:45:55 +0800766 test.update_state(**invoc.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800767 del self.invocations[test]
Jon Salz1acc8742012-07-17 17:45:55 +0800768
Jon Salz0697cbf2012-07-04 15:14:04 +0800769 if not abort:
770 test.update_state(status=TestState.UNTESTED)
771 self.reap_completed_tests()
772
Jon Salz85a39882012-07-05 16:45:04 +0800773 def stop(self, root=None, fail=False):
774 self.kill_active_tests(fail, root)
775 # Remove any tests in the run queue under the root.
776 self.tests_to_run = deque([x for x in self.tests_to_run
777 if root and not x.has_ancestor(root)])
778 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800779
780 def abort_active_tests(self):
781 self.kill_active_tests(True)
782
783 def main(self):
784 try:
785 self.init()
786 self.event_log.Log('goofy_init',
787 success=True)
788 except:
789 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800790 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800791 self.event_log.Log('goofy_init',
792 success=False,
793 trace=traceback.format_exc())
794 except: # pylint: disable=W0702
795 pass
796 raise
797
798 self.run()
799
800 def update_system_info(self):
801 '''Updates system info.'''
802 system_info = system.SystemInfo()
803 self.state_instance.set_shared_data('system_info', system_info.__dict__)
804 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
805 system_info=system_info.__dict__))
806 logging.info('System info: %r', system_info.__dict__)
807
Jon Salz5c344f62012-07-13 14:31:16 +0800808 def update_factory(self, auto_run_on_restart=False):
809 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800810 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +0800811 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +0800812
Jon Salz5c344f62012-07-13 14:31:16 +0800813 def pre_update_hook():
814 if auto_run_on_restart:
815 self.state_instance.set_shared_data('tests_after_shutdown',
816 FORCE_AUTO_RUN)
817 self.state_instance.close()
818
Jon Salz0697cbf2012-07-04 15:14:04 +0800819 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800820 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800821 self.env.shutdown('reboot')
822 except: # pylint: disable=W0702
823 factory.console.exception('Unable to update')
824
825 def init(self, args=None, env=None):
826 '''Initializes Goofy.
827
828 Args:
829 args: A list of command-line arguments. Uses sys.argv if
830 args is None.
831 env: An Environment instance to use (or None to choose
832 FakeChrootEnvironment or DUTEnvironment as appropriate).
833 '''
834 parser = OptionParser()
835 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +0800836 action='store_true',
837 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +0800838 parser.add_option('--print_test_list', dest='print_test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800839 metavar='FILE',
840 help='Read and print test list FILE, and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +0800841 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +0800842 action='store_true',
843 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +0800844 parser.add_option('--ui', dest='ui', type='choice',
Jon Salz8fa8e832012-07-13 19:04:09 +0800845 choices=['none', 'gtk', 'chrome'],
846 default=('chrome' if utils.in_chroot() else 'gtk'),
847 help='UI to use')
Jon Salz0697cbf2012-07-04 15:14:04 +0800848 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
Jon Salz8fa8e832012-07-13 19:04:09 +0800849 type='int', default=1,
850 help=('Factor by which to scale UI '
851 '(Chrome UI only)'))
Jon Salz0697cbf2012-07-04 15:14:04 +0800852 parser.add_option('--test_list', dest='test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800853 metavar='FILE',
854 help='Use FILE as test list')
Jon Salz0697cbf2012-07-04 15:14:04 +0800855 (self.options, self.args) = parser.parse_args(args)
856
Jon Salz46b89562012-07-05 11:49:22 +0800857 # Make sure factory directories exist.
858 factory.get_log_root()
859 factory.get_state_root()
860 factory.get_test_data_root()
861
Jon Salz0697cbf2012-07-04 15:14:04 +0800862 global _inited_logging # pylint: disable=W0603
863 if not _inited_logging:
864 factory.init_logging('goofy', verbose=self.options.verbose)
865 _inited_logging = True
Jon Salz8fa8e832012-07-13 19:04:09 +0800866
Jon Salz0697cbf2012-07-04 15:14:04 +0800867 self.event_log = EventLog('goofy')
868
869 if (not suppress_chroot_warning and
870 factory.in_chroot() and
871 self.options.ui == 'gtk' and
872 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
873 # That's not going to work! Tell the user how to run
874 # this way.
875 logging.warn(GOOFY_IN_CHROOT_WARNING)
876 time.sleep(1)
877
878 if env:
879 self.env = env
880 elif factory.in_chroot():
881 self.env = test_environment.FakeChrootEnvironment()
882 logging.warn(
883 'Using chroot environment: will not actually run autotests')
884 else:
885 self.env = test_environment.DUTEnvironment()
886 self.env.goofy = self
887
888 if self.options.restart:
889 state.clear_state()
890
891 if self.options.print_test_list:
892 print (factory.read_test_list(
893 self.options.print_test_list,
894 test_classes=dict(test_steps.__dict__)).
895 __repr__(recursive=True))
896 return
897
898 if self.options.ui_scale_factor != 1 and utils.in_qemu():
899 logging.warn(
900 'In QEMU; ignoring ui_scale_factor argument')
901 self.options.ui_scale_factor = 1
902
903 logging.info('Started')
904
905 self.start_state_server()
906 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
907 self.state_instance.set_shared_data('ui_scale_factor',
908 self.options.ui_scale_factor)
909 self.last_shutdown_time = (
910 self.state_instance.get_shared_data('shutdown_time', optional=True))
911 self.state_instance.del_shared_data('shutdown_time', optional=True)
912
913 if not self.options.test_list:
914 self.options.test_list = find_test_list()
915 if not self.options.test_list:
916 logging.error('No test list. Aborting.')
917 sys.exit(1)
918 logging.info('Using test list %s', self.options.test_list)
919
920 self.test_list = factory.read_test_list(
921 self.options.test_list,
922 self.state_instance,
923 test_classes=dict(test_steps.__dict__))
924 if not self.state_instance.has_shared_data('ui_lang'):
925 self.state_instance.set_shared_data('ui_lang',
926 self.test_list.options.ui_lang)
927 self.state_instance.set_shared_data(
928 'test_list_options',
929 self.test_list.options.__dict__)
930 self.state_instance.test_list = self.test_list
931
Jon Salz8fa8e832012-07-13 19:04:09 +0800932 if self.test_list.options.time_sanitizer:
933 self.time_sanitizer = time_sanitizer.TimeSanitizer(
934 base_time=time_sanitizer.GetBaseTimeFromFile(
935 # lsb-factory is written by the factory install shim during
936 # installation, so it should have a good time obtained from
937 # the mini-Omaha server.
938 '/usr/local/etc/lsb-factory'))
939 self.time_sanitizer.RunOnce()
940
Jon Salz0697cbf2012-07-04 15:14:04 +0800941 self.init_states()
942 self.start_event_server()
943 self.connection_manager = self.env.create_connection_manager(
944 self.test_list.options.wlans)
945 # Note that we create a log watcher even if
946 # sync_event_log_period_secs isn't set (no background
947 # syncing), since we may use it to flush event logs as well.
948 self.log_watcher = EventLogWatcher(
949 self.test_list.options.sync_event_log_period_secs,
950 handle_event_logs_callback=self._handle_event_logs)
951 if self.test_list.options.sync_event_log_period_secs:
952 self.log_watcher.StartWatchThread()
953
954 self.update_system_info()
955
956 os.environ['CROS_FACTORY'] = '1'
957 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
958
959 # Set CROS_UI since some behaviors in ui.py depend on the
960 # particular UI in use. TODO(jsalz): Remove this (and all
961 # places it is used) when the GTK UI is removed.
962 os.environ['CROS_UI'] = self.options.ui
963
964 if self.options.ui == 'chrome':
965 self.env.launch_chrome()
966 logging.info('Waiting for a web socket connection')
967 self.web_socket_manager.wait()
968
969 # Wait for the test widget size to be set; this is done in
970 # an asynchronous RPC so there is a small chance that the
971 # web socket might be opened first.
972 for _ in range(100): # 10 s
973 try:
974 if self.state_instance.get_shared_data('test_widget_size'):
975 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800976 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800977 pass # Retry
978 time.sleep(0.1) # 100 ms
979 else:
980 logging.warn('Never received test_widget_size from UI')
981 elif self.options.ui == 'gtk':
982 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800983
Jon Salz0697cbf2012-07-04 15:14:04 +0800984 def state_change_callback(test, test_state):
985 self.event_client.post_event(
986 Event(Event.Type.STATE_CHANGE,
987 path=test.path, state=test_state))
988 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800989
Jon Salza6711d72012-07-18 14:33:03 +0800990 for handler in self.on_ui_startup:
991 handler()
992
993 self.prespawner = Prespawner()
994 self.prespawner.start()
995
Jon Salz0697cbf2012-07-04 15:14:04 +0800996 try:
997 tests_after_shutdown = self.state_instance.get_shared_data(
998 'tests_after_shutdown')
999 except KeyError:
1000 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +08001001
Jon Salz5c344f62012-07-13 14:31:16 +08001002 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
1003 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +08001004 logging.info('Resuming tests after shutdown: %s',
1005 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +08001006 self.tests_to_run.extend(
1007 self.test_list.lookup_path(t) for t in tests_after_shutdown)
1008 self.run_queue.put(self.run_next_test)
1009 else:
Jon Salz5c344f62012-07-13 14:31:16 +08001010 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +08001011 self.run_queue.put(
1012 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +08001013 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001014
Jon Salz0697cbf2012-07-04 15:14:04 +08001015 def run(self):
1016 '''Runs Goofy.'''
1017 # Process events forever.
1018 while self.run_once(True):
1019 pass
Jon Salz73e0fd02012-04-04 11:46:38 +08001020
Jon Salz0697cbf2012-07-04 15:14:04 +08001021 def run_once(self, block=False):
1022 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001023
Jon Salz0697cbf2012-07-04 15:14:04 +08001024 Args:
1025 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +08001026
Jon Salz0697cbf2012-07-04 15:14:04 +08001027 Returns:
1028 True to keep going or False to shut down.
1029 '''
1030 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +08001031 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +08001032 # Nothing on the run queue.
1033 self._run_queue_idle()
1034 if block:
1035 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +08001036 try:
1037 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
1038 except Queue.Empty:
1039 # Keep going (calling _run_queue_idle() again at the top of
1040 # the loop)
1041 continue
Jon Salz0697cbf2012-07-04 15:14:04 +08001042 # ...and grab anything else that showed up at the same
1043 # time.
1044 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +08001045 else:
1046 break
Jon Salz51528e12012-07-02 18:54:45 +08001047
Jon Salz0697cbf2012-07-04 15:14:04 +08001048 for event in events:
1049 if not event:
1050 # Shutdown request.
1051 self.run_queue.task_done()
1052 return False
Jon Salz51528e12012-07-02 18:54:45 +08001053
Jon Salz0697cbf2012-07-04 15:14:04 +08001054 try:
1055 event()
Jon Salz85a39882012-07-05 16:45:04 +08001056 except: # pylint: disable=W0702
1057 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001058 self.record_exception(traceback.format_exception_only(
1059 *sys.exc_info()[:2]))
1060 # But keep going
1061 finally:
1062 self.run_queue.task_done()
1063 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001064
Jon Salz8fa8e832012-07-13 19:04:09 +08001065 def sync_time_in_background(self):
1066 '''Attempts to sync time with the shopfloor server.'''
1067 if ((not self.test_list.options.sync_time_period_secs) or
1068 (not self.time_sanitizer) or
1069 self.time_synced or
1070 factory.in_chroot()):
1071 # Not enabled or already succeeded.
1072 return
1073
1074 now = time.time()
1075 if self.last_sync_time and (
1076 now - self.last_sync_time <
1077 self.test_list.options.sync_time_period_secs):
1078 # Not yet time for another check.
1079 return
1080 self.last_sync_time = now
1081
1082 def target():
1083 try:
1084 self.time_sanitizer.SyncWithShopfloor()
1085 self.time_synced = True
1086 except: # pylint: disable=W0702
1087 # Oh well. Log an error (but no trace)
1088 logging.info(
1089 'Unable to get time from shopfloor server: %s',
1090 utils.FormatExceptionOnly())
1091
1092 thread = threading.Thread(target=target)
1093 thread.daemon = True
1094 thread.start()
1095
Jon Salz0697cbf2012-07-04 15:14:04 +08001096 def _run_queue_idle(self):
1097 '''Invoked when the run queue has no events.'''
1098 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001099 self.check_for_updates()
Jon Salz8fa8e832012-07-13 19:04:09 +08001100 self.sync_time_in_background()
Jon Salz57717ca2012-04-04 16:47:25 +08001101
Jon Salz0697cbf2012-07-04 15:14:04 +08001102 def _handle_event_logs(self, log_name, chunk):
1103 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001104
Jon Salz0697cbf2012-07-04 15:14:04 +08001105 Attempts to upload the event logs to the shopfloor server.
1106 '''
1107 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1108 start_time = time.time()
1109 logging.info('Syncing %s', description)
1110 shopfloor_client = shopfloor.get_instance(
1111 detect=True,
1112 timeout=self.test_list.options.shopfloor_timeout_secs)
1113 shopfloor_client.UploadEvent(log_name, chunk)
1114 logging.info(
1115 'Successfully synced %s in %.03f s',
1116 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001117
Jon Salz0697cbf2012-07-04 15:14:04 +08001118 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1119 root=None):
1120 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001121
Jon Salz0697cbf2012-07-04 15:14:04 +08001122 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001123
Jon Salz0697cbf2012-07-04 15:14:04 +08001124 Args:
1125 starting_at: If provided, only auto-runs tests beginning with
1126 this test.
1127 '''
1128 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001129
Jon Salz0697cbf2012-07-04 15:14:04 +08001130 if starting_at:
1131 # Make sure they passed a test, not a string.
1132 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001133
Jon Salz0697cbf2012-07-04 15:14:04 +08001134 tests_to_reset = []
1135 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001136
Jon Salz0697cbf2012-07-04 15:14:04 +08001137 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001138
Jon Salz0697cbf2012-07-04 15:14:04 +08001139 for test in root.get_top_level_tests():
1140 if starting_at:
1141 if test == starting_at:
1142 # We've found starting_at; do auto-run on all
1143 # subsequent tests.
1144 found_starting_at = True
1145 if not found_starting_at:
1146 # Don't start this guy yet
1147 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001148
Jon Salz0697cbf2012-07-04 15:14:04 +08001149 status = test.get_state().status
1150 if status == TestState.ACTIVE or status in statuses_to_run:
1151 # Reset the test (later; we will need to abort
1152 # all active tests first).
1153 tests_to_reset.append(test)
1154 if status in statuses_to_run:
1155 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001156
Jon Salz0697cbf2012-07-04 15:14:04 +08001157 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001158
Jon Salz0697cbf2012-07-04 15:14:04 +08001159 # Reset all statuses of the tests to run (in case any tests were active;
1160 # we want them to be run again).
1161 for test_to_reset in tests_to_reset:
1162 for test in test_to_reset.walk():
1163 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001164
Jon Salz0697cbf2012-07-04 15:14:04 +08001165 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001166
Jon Salz0697cbf2012-07-04 15:14:04 +08001167 def restart_tests(self, root=None):
1168 '''Restarts all tests.'''
1169 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001170
Jon Salz0697cbf2012-07-04 15:14:04 +08001171 self.abort_active_tests()
1172 for test in root.walk():
1173 test.update_state(status=TestState.UNTESTED)
1174 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001175
Jon Salz0697cbf2012-07-04 15:14:04 +08001176 def auto_run(self, starting_at=None, root=None):
1177 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001178
Jon Salz0697cbf2012-07-04 15:14:04 +08001179 Args:
1180 starting_at: If provide, only auto-runs tests beginning with
1181 this test.
1182 '''
1183 root = root or self.test_list
1184 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1185 starting_at=starting_at,
1186 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001187
Jon Salz0697cbf2012-07-04 15:14:04 +08001188 def re_run_failed(self, root=None):
1189 '''Re-runs failed tests.'''
1190 root = root or self.test_list
1191 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001192
Jon Salz0697cbf2012-07-04 15:14:04 +08001193 def show_review_information(self):
1194 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001195
Jon Salz0697cbf2012-07-04 15:14:04 +08001196 The information screene is rendered by main UI program (ui.py), so in
1197 goofy we only need to kill all active tests, set them as untested, and
1198 clear remaining tests.
1199 '''
1200 self.kill_active_tests(False)
Jon Salza6711d72012-07-18 14:33:03 +08001201 self.cancel_pending_tests()
Jon Salz57717ca2012-04-04 16:47:25 +08001202
Jon Salz0697cbf2012-07-04 15:14:04 +08001203 def handle_switch_test(self, event):
1204 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001205
Jon Salz0697cbf2012-07-04 15:14:04 +08001206 @param event: The SWITCH_TEST event.
1207 '''
1208 test = self.test_list.lookup_path(event.path)
1209 if not test:
1210 logging.error('Unknown test %r', event.key)
1211 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001212
Jon Salz0697cbf2012-07-04 15:14:04 +08001213 invoc = self.invocations.get(test)
1214 if invoc and test.backgroundable:
1215 # Already running: just bring to the front if it
1216 # has a UI.
1217 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001218 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001219 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001220
Jon Salz0697cbf2012-07-04 15:14:04 +08001221 self.abort_active_tests()
1222 for t in test.walk():
1223 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001224
Jon Salz0697cbf2012-07-04 15:14:04 +08001225 if self.test_list.options.auto_run_on_keypress:
1226 self.auto_run(starting_at=test)
1227 else:
1228 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001229
Jon Salz0697cbf2012-07-04 15:14:04 +08001230 def wait(self):
1231 '''Waits for all pending invocations.
1232
1233 Useful for testing.
1234 '''
Jon Salz1acc8742012-07-17 17:45:55 +08001235 while self.invocations:
1236 for k, v in self.invocations.iteritems():
1237 logging.info('Waiting for %s to complete...', k)
1238 v.thread.join()
1239 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001240
1241 def check_exceptions(self):
1242 '''Raises an error if any exceptions have occurred in
1243 invocation threads.'''
1244 if self.exceptions:
1245 raise RuntimeError('Exception in invocation thread: %r' %
1246 self.exceptions)
1247
1248 def record_exception(self, msg):
1249 '''Records an exception in an invocation thread.
1250
1251 An exception with the given message will be rethrown when
1252 Goofy is destroyed.'''
1253 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001254
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001255
1256if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001257 Goofy().main()