blob: 6bc1a1461c389b1044503b480c6e25bf04ceac74 [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')
382 else:
383 def handler():
384 if self._prompt_cancel_shutdown(
385 test, test_state.shutdown_count + 1):
386 log_and_update_state(
387 status=TestState.FAILED,
388 error_msg='Shutdown aborted by operator')
389 return
Jon Salz0405ab52012-03-16 15:26:52 +0800390
Jon Salz0697cbf2012-07-04 15:14:04 +0800391 # Time to shutdown again
392 log_and_update_state(
393 status=TestState.ACTIVE,
394 error_msg='',
395 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800396
Jon Salz0697cbf2012-07-04 15:14:04 +0800397 self.event_log.Log('shutdown', operation='reboot')
398 self.state_instance.set_shared_data('shutdown_time',
399 time.time())
400 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800401
Jon Salz0697cbf2012-07-04 15:14:04 +0800402 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800403
Jon Salz0697cbf2012-07-04 15:14:04 +0800404 def _prompt_cancel_shutdown(self, test, iteration):
405 if self.options.ui != 'chrome':
406 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800407
Jon Salz0697cbf2012-07-04 15:14:04 +0800408 pending_shutdown_data = {
409 'delay_secs': test.delay_secs,
410 'time': time.time() + test.delay_secs,
411 'operation': test.operation,
412 'iteration': iteration,
413 'iterations': test.iterations,
414 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415
Jon Salz0697cbf2012-07-04 15:14:04 +0800416 # Create a new (threaded) event client since we
417 # don't want to use the event loop for this.
418 with EventClient() as event_client:
419 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
420 **pending_shutdown_data))
421 aborted = event_client.wait(
422 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
423 timeout=test.delay_secs) is not None
424 if aborted:
425 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
426 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800427
Jon Salz0697cbf2012-07-04 15:14:04 +0800428 def init_states(self):
429 '''
430 Initializes all states on startup.
431 '''
432 for test in self.test_list.get_all_tests():
433 # Make sure the state server knows about all the tests,
434 # defaulting to an untested state.
435 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800436
Jon Salz0697cbf2012-07-04 15:14:04 +0800437 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800438
Jon Salz0697cbf2012-07-04 15:14:04 +0800439 # Any 'active' tests should be marked as failed now.
440 for test in self.test_list.walk():
441 test_state = test.get_state()
442 if test_state.status != TestState.ACTIVE:
443 continue
444 if isinstance(test, factory.ShutdownStep):
445 # Shutdown while the test was active - that's good.
446 self.handle_shutdown_complete(test, test_state)
447 else:
448 # Unexpected shutdown. Grab /var/log/messages for context.
449 if var_log_messages is None:
450 try:
451 var_log_messages = (
452 utils.var_log_messages_before_reboot())
453 # Write it to the log, to make it easier to
454 # correlate with /var/log/messages.
455 logging.info(
456 'Unexpected shutdown. '
457 'Tail of /var/log/messages before last reboot:\n'
458 '%s', ('\n'.join(
459 ' ' + x for x in var_log_messages)))
460 except: # pylint: disable=W0702
461 logging.exception('Unable to grok /var/log/messages')
462 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800463
Jon Salz0697cbf2012-07-04 15:14:04 +0800464 error_msg = 'Unexpected shutdown while test was running'
465 self.event_log.Log('end_test',
466 path=test.path,
467 status=TestState.FAILED,
468 invocation=test.get_state().invocation,
469 error_msg=error_msg,
470 var_log_messages='\n'.join(var_log_messages))
471 test.update_state(
472 status=TestState.FAILED,
473 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800474
Jon Salz0697cbf2012-07-04 15:14:04 +0800475 def show_next_active_test(self):
476 '''
477 Rotates to the next visible active test.
478 '''
479 self.reap_completed_tests()
480 active_tests = [
481 t for t in self.test_list.walk()
482 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
483 if not active_tests:
484 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800485
Jon Salz0697cbf2012-07-04 15:14:04 +0800486 try:
487 next_test = active_tests[
488 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
489 except ValueError: # visible_test not present in active_tests
490 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800491
Jon Salz0697cbf2012-07-04 15:14:04 +0800492 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800493
Jon Salz0697cbf2012-07-04 15:14:04 +0800494 def handle_event(self, event):
495 '''
496 Handles an event from the event server.
497 '''
498 handler = self.event_handlers.get(event.type)
499 if handler:
500 handler(event)
501 else:
502 # We don't register handlers for all event types - just ignore
503 # this event.
504 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800505
Jon Salz0697cbf2012-07-04 15:14:04 +0800506 def run_next_test(self):
507 '''
508 Runs the next eligible test (or tests) in self.tests_to_run.
509 '''
510 self.reap_completed_tests()
511 while self.tests_to_run:
512 logging.debug('Tests to run: %s',
513 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800514
Jon Salz0697cbf2012-07-04 15:14:04 +0800515 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800516
Jon Salz0697cbf2012-07-04 15:14:04 +0800517 if test in self.invocations:
518 logging.info('Next test %s is already running', test.path)
519 self.tests_to_run.popleft()
520 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800521
Jon Salz304a75d2012-07-06 11:14:15 +0800522 for i in test.require_run:
523 for j in i.walk():
524 if j.get_state().status == TestState.ACTIVE:
525 logging.info('Waiting for active test %s to complete '
526 'before running %s', j.path, test.path)
527 return
528
Jon Salz0697cbf2012-07-04 15:14:04 +0800529 if self.invocations and not (test.backgroundable and all(
530 [x.backgroundable for x in self.invocations])):
531 logging.debug('Waiting for non-backgroundable tests to '
532 'complete before running %s', test.path)
533 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800534
Jon Salz0697cbf2012-07-04 15:14:04 +0800535 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800536
Jon Salz304a75d2012-07-06 11:14:15 +0800537 untested = set()
538 for i in test.require_run:
539 for j in i.walk():
540 if j == test:
541 # We've hit this test itself; stop checking
542 break
543 if j.get_state().status == TestState.UNTESTED:
544 # Found an untested test; move on to the next
545 # element in require_run.
546 untested.add(j)
547 break
548
549 if untested:
550 untested_paths = ', '.join(sorted([x.path for x in untested]))
551 if self.state_instance.get_shared_data('engineering_mode',
552 optional=True):
553 # In engineering mode, we'll let it go.
554 factory.console.warn('In engineering mode; running '
555 '%s even though required tests '
556 '[%s] have not completed',
557 test.path, untested_paths)
558 else:
559 # Not in engineering mode; mark it failed.
560 error_msg = ('Required tests [%s] have not been run yet'
561 % untested_paths)
562 factory.console.error('Not running %s: %s',
563 test.path, error_msg)
564 test.update_state(status=TestState.FAILED,
565 error_msg=error_msg)
566 continue
567
Jon Salz0697cbf2012-07-04 15:14:04 +0800568 if isinstance(test, factory.ShutdownStep):
569 if os.path.exists(NO_REBOOT_FILE):
570 test.update_state(
571 status=TestState.FAILED, increment_count=1,
572 error_msg=('Skipped shutdown since %s is present' %
Jon Salz304a75d2012-07-06 11:14:15 +0800573 NO_REBOOT_FILE))
Jon Salz0697cbf2012-07-04 15:14:04 +0800574 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800575
Jon Salz0697cbf2012-07-04 15:14:04 +0800576 test.update_state(status=TestState.ACTIVE, increment_count=1,
577 error_msg='', shutdown_count=0)
578 if self._prompt_cancel_shutdown(test, 1):
579 self.event_log.Log('reboot_cancelled')
580 test.update_state(
581 status=TestState.FAILED, increment_count=1,
582 error_msg='Shutdown aborted by operator',
583 shutdown_count=0)
584 return
Jon Salz2f757d42012-06-27 17:06:42 +0800585
Jon Salz0697cbf2012-07-04 15:14:04 +0800586 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800587 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800588 'tests_after_shutdown',
589 [t.path for t in self.tests_to_run])
590 # Save shutdown time
591 self.state_instance.set_shared_data('shutdown_time',
592 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800593
Jon Salz0697cbf2012-07-04 15:14:04 +0800594 with self.env.lock:
595 self.event_log.Log('shutdown', operation=test.operation)
596 shutdown_result = self.env.shutdown(test.operation)
597 if shutdown_result:
598 # That's all, folks!
599 self.run_queue.put(None)
600 return
601 else:
602 # Just pass (e.g., in the chroot).
603 test.update_state(status=TestState.PASSED)
604 self.state_instance.set_shared_data(
605 'tests_after_shutdown', None)
606 # Send event with no fields to indicate that there is no
607 # longer a pending shutdown.
608 self.event_client.post_event(Event(
609 Event.Type.PENDING_SHUTDOWN))
610 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800611
Jon Salz1acc8742012-07-17 17:45:55 +0800612 self._run_test(test, test.iterations)
613
614 def _run_test(self, test, iterations_left=None):
615 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
616 new_state = test.update_state(
617 status=TestState.ACTIVE, increment_count=1, error_msg='',
618 invocation=invoc.uuid, iterations_left=iterations_left)
619 invoc.count = new_state.count
620
621 self.invocations[test] = invoc
622 if self.visible_test is None and test.has_ui:
623 self.set_visible_test(test)
624 self.check_connection_manager()
625 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800626
Jon Salz0697cbf2012-07-04 15:14:04 +0800627 def check_connection_manager(self):
628 exclusive_tests = [
629 test.path
630 for test in self.invocations
631 if test.is_exclusive(
632 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
633 if exclusive_tests:
634 # Make sure networking is disabled.
635 if self.network_enabled:
636 logging.info('Disabling network, as requested by %s',
637 exclusive_tests)
638 self.connection_manager.DisableNetworking()
639 self.network_enabled = False
640 else:
641 # Make sure networking is enabled.
642 if not self.network_enabled:
643 logging.info('Re-enabling network')
644 self.connection_manager.EnableNetworking()
645 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800646
cychiang21886742012-07-05 15:16:32 +0800647 def check_for_updates(self):
648 '''
649 Schedules an asynchronous check for updates if necessary.
650 '''
651 if not self.test_list.options.update_period_secs:
652 # Not enabled.
653 return
654
655 now = time.time()
656 if self.last_update_check and (
657 now - self.last_update_check <
658 self.test_list.options.update_period_secs):
659 # Not yet time for another check.
660 return
661
662 self.last_update_check = now
663
664 def handle_check_for_update(reached_shopfloor, md5sum, needs_update):
665 if reached_shopfloor:
666 new_update_md5sum = md5sum if needs_update else None
667 if system.SystemInfo.update_md5sum != new_update_md5sum:
668 logging.info('Received new update MD5SUM: %s', new_update_md5sum)
669 system.SystemInfo.update_md5sum = new_update_md5sum
670 self.run_queue.put(self.update_system_info)
671
672 updater.CheckForUpdateAsync(
673 handle_check_for_update,
674 self.test_list.options.shopfloor_timeout_secs)
675
Jon Salz0697cbf2012-07-04 15:14:04 +0800676 def run_tests(self, subtrees, untested_only=False):
677 '''
678 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800679
Jon Salz0697cbf2012-07-04 15:14:04 +0800680 The tests are run in order unless one fails (then stops).
681 Backgroundable tests are run simultaneously; when a foreground test is
682 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800683
Jon Salz0697cbf2012-07-04 15:14:04 +0800684 @param subtrees: Node or nodes containing tests to run (may either be
685 a single test or a list). Duplicates will be ignored.
686 '''
687 if type(subtrees) != list:
688 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800689
Jon Salz0697cbf2012-07-04 15:14:04 +0800690 # Nodes we've seen so far, to avoid duplicates.
691 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800692
Jon Salz0697cbf2012-07-04 15:14:04 +0800693 self.tests_to_run = deque()
694 for subtree in subtrees:
695 for test in subtree.walk():
696 if test in seen:
697 continue
698 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800699
Jon Salz0697cbf2012-07-04 15:14:04 +0800700 if not test.is_leaf():
701 continue
702 if (untested_only and
703 test.get_state().status != TestState.UNTESTED):
704 continue
705 self.tests_to_run.append(test)
706 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800707
Jon Salz0697cbf2012-07-04 15:14:04 +0800708 def reap_completed_tests(self):
709 '''
710 Removes completed tests from the set of active tests.
711
712 Also updates the visible test if it was reaped.
713 '''
714 for t, v in dict(self.invocations).iteritems():
715 if v.is_completed():
Jon Salz1acc8742012-07-17 17:45:55 +0800716 new_state = t.update_state(**v.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800717 del self.invocations[t]
718
Jon Salz1acc8742012-07-17 17:45:55 +0800719 if new_state.iterations_left and new_state.status == TestState.PASSED:
720 # Play it again, Sam!
721 self._run_test(t)
722
Jon Salz0697cbf2012-07-04 15:14:04 +0800723 if (self.visible_test is None or
Jon Salz85a39882012-07-05 16:45:04 +0800724 self.visible_test not in self.invocations):
Jon Salz0697cbf2012-07-04 15:14:04 +0800725 self.set_visible_test(None)
726 # Make the first running test, if any, the visible test
727 for t in self.test_list.walk():
728 if t in self.invocations:
729 self.set_visible_test(t)
730 break
731
Jon Salz85a39882012-07-05 16:45:04 +0800732 def kill_active_tests(self, abort, root=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800733 '''
734 Kills and waits for all active tests.
735
Jon Salz85a39882012-07-05 16:45:04 +0800736 Args:
737 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800738 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800739 root: If set, only kills tests with root as an ancestor.
Jon Salz0697cbf2012-07-04 15:14:04 +0800740 '''
741 self.reap_completed_tests()
742 for test, invoc in self.invocations.items():
Jon Salz85a39882012-07-05 16:45:04 +0800743 if root and not test.has_ancestor(root):
744 continue
745
Jon Salz0697cbf2012-07-04 15:14:04 +0800746 factory.console.info('Killing active test %s...' % test.path)
747 invoc.abort_and_join()
748 factory.console.info('Killed %s' % test.path)
Jon Salz1acc8742012-07-17 17:45:55 +0800749 test.update_state(**invoc.update_state_on_completion)
Jon Salz0697cbf2012-07-04 15:14:04 +0800750 del self.invocations[test]
Jon Salz1acc8742012-07-17 17:45:55 +0800751
Jon Salz0697cbf2012-07-04 15:14:04 +0800752 if not abort:
753 test.update_state(status=TestState.UNTESTED)
754 self.reap_completed_tests()
755
Jon Salz85a39882012-07-05 16:45:04 +0800756 def stop(self, root=None, fail=False):
757 self.kill_active_tests(fail, root)
758 # Remove any tests in the run queue under the root.
759 self.tests_to_run = deque([x for x in self.tests_to_run
760 if root and not x.has_ancestor(root)])
761 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800762
763 def abort_active_tests(self):
764 self.kill_active_tests(True)
765
766 def main(self):
767 try:
768 self.init()
769 self.event_log.Log('goofy_init',
770 success=True)
771 except:
772 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800773 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800774 self.event_log.Log('goofy_init',
775 success=False,
776 trace=traceback.format_exc())
777 except: # pylint: disable=W0702
778 pass
779 raise
780
781 self.run()
782
783 def update_system_info(self):
784 '''Updates system info.'''
785 system_info = system.SystemInfo()
786 self.state_instance.set_shared_data('system_info', system_info.__dict__)
787 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
788 system_info=system_info.__dict__))
789 logging.info('System info: %r', system_info.__dict__)
790
Jon Salz5c344f62012-07-13 14:31:16 +0800791 def update_factory(self, auto_run_on_restart=False):
792 '''Commences updating factory software.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800793 self.kill_active_tests(False)
794 self.run_tests([])
795
Jon Salz5c344f62012-07-13 14:31:16 +0800796 def pre_update_hook():
797 if auto_run_on_restart:
798 self.state_instance.set_shared_data('tests_after_shutdown',
799 FORCE_AUTO_RUN)
800 self.state_instance.close()
801
Jon Salz0697cbf2012-07-04 15:14:04 +0800802 try:
Jon Salz5c344f62012-07-13 14:31:16 +0800803 if updater.TryUpdate(pre_update_hook=pre_update_hook):
Jon Salz0697cbf2012-07-04 15:14:04 +0800804 self.env.shutdown('reboot')
805 except: # pylint: disable=W0702
806 factory.console.exception('Unable to update')
807
808 def init(self, args=None, env=None):
809 '''Initializes Goofy.
810
811 Args:
812 args: A list of command-line arguments. Uses sys.argv if
813 args is None.
814 env: An Environment instance to use (or None to choose
815 FakeChrootEnvironment or DUTEnvironment as appropriate).
816 '''
817 parser = OptionParser()
818 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +0800819 action='store_true',
820 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +0800821 parser.add_option('--print_test_list', dest='print_test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800822 metavar='FILE',
823 help='Read and print test list FILE, and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +0800824 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +0800825 action='store_true',
826 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +0800827 parser.add_option('--ui', dest='ui', type='choice',
Jon Salz8fa8e832012-07-13 19:04:09 +0800828 choices=['none', 'gtk', 'chrome'],
829 default=('chrome' if utils.in_chroot() else 'gtk'),
830 help='UI to use')
Jon Salz0697cbf2012-07-04 15:14:04 +0800831 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
Jon Salz8fa8e832012-07-13 19:04:09 +0800832 type='int', default=1,
833 help=('Factor by which to scale UI '
834 '(Chrome UI only)'))
Jon Salz0697cbf2012-07-04 15:14:04 +0800835 parser.add_option('--test_list', dest='test_list',
Jon Salz8fa8e832012-07-13 19:04:09 +0800836 metavar='FILE',
837 help='Use FILE as test list')
Jon Salz0697cbf2012-07-04 15:14:04 +0800838 (self.options, self.args) = parser.parse_args(args)
839
Jon Salz46b89562012-07-05 11:49:22 +0800840 # Make sure factory directories exist.
841 factory.get_log_root()
842 factory.get_state_root()
843 factory.get_test_data_root()
844
Jon Salz0697cbf2012-07-04 15:14:04 +0800845 global _inited_logging # pylint: disable=W0603
846 if not _inited_logging:
847 factory.init_logging('goofy', verbose=self.options.verbose)
848 _inited_logging = True
Jon Salz8fa8e832012-07-13 19:04:09 +0800849
Jon Salz0697cbf2012-07-04 15:14:04 +0800850 self.event_log = EventLog('goofy')
851
852 if (not suppress_chroot_warning and
853 factory.in_chroot() and
854 self.options.ui == 'gtk' and
855 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
856 # That's not going to work! Tell the user how to run
857 # this way.
858 logging.warn(GOOFY_IN_CHROOT_WARNING)
859 time.sleep(1)
860
861 if env:
862 self.env = env
863 elif factory.in_chroot():
864 self.env = test_environment.FakeChrootEnvironment()
865 logging.warn(
866 'Using chroot environment: will not actually run autotests')
867 else:
868 self.env = test_environment.DUTEnvironment()
869 self.env.goofy = self
870
871 if self.options.restart:
872 state.clear_state()
873
874 if self.options.print_test_list:
875 print (factory.read_test_list(
876 self.options.print_test_list,
877 test_classes=dict(test_steps.__dict__)).
878 __repr__(recursive=True))
879 return
880
881 if self.options.ui_scale_factor != 1 and utils.in_qemu():
882 logging.warn(
883 'In QEMU; ignoring ui_scale_factor argument')
884 self.options.ui_scale_factor = 1
885
886 logging.info('Started')
887
888 self.start_state_server()
889 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
890 self.state_instance.set_shared_data('ui_scale_factor',
891 self.options.ui_scale_factor)
892 self.last_shutdown_time = (
893 self.state_instance.get_shared_data('shutdown_time', optional=True))
894 self.state_instance.del_shared_data('shutdown_time', optional=True)
895
896 if not self.options.test_list:
897 self.options.test_list = find_test_list()
898 if not self.options.test_list:
899 logging.error('No test list. Aborting.')
900 sys.exit(1)
901 logging.info('Using test list %s', self.options.test_list)
902
903 self.test_list = factory.read_test_list(
904 self.options.test_list,
905 self.state_instance,
906 test_classes=dict(test_steps.__dict__))
907 if not self.state_instance.has_shared_data('ui_lang'):
908 self.state_instance.set_shared_data('ui_lang',
909 self.test_list.options.ui_lang)
910 self.state_instance.set_shared_data(
911 'test_list_options',
912 self.test_list.options.__dict__)
913 self.state_instance.test_list = self.test_list
914
Jon Salz8fa8e832012-07-13 19:04:09 +0800915 if self.test_list.options.time_sanitizer:
916 self.time_sanitizer = time_sanitizer.TimeSanitizer(
917 base_time=time_sanitizer.GetBaseTimeFromFile(
918 # lsb-factory is written by the factory install shim during
919 # installation, so it should have a good time obtained from
920 # the mini-Omaha server.
921 '/usr/local/etc/lsb-factory'))
922 self.time_sanitizer.RunOnce()
923
Jon Salz0697cbf2012-07-04 15:14:04 +0800924 self.init_states()
925 self.start_event_server()
926 self.connection_manager = self.env.create_connection_manager(
927 self.test_list.options.wlans)
928 # Note that we create a log watcher even if
929 # sync_event_log_period_secs isn't set (no background
930 # syncing), since we may use it to flush event logs as well.
931 self.log_watcher = EventLogWatcher(
932 self.test_list.options.sync_event_log_period_secs,
933 handle_event_logs_callback=self._handle_event_logs)
934 if self.test_list.options.sync_event_log_period_secs:
935 self.log_watcher.StartWatchThread()
936
937 self.update_system_info()
938
939 os.environ['CROS_FACTORY'] = '1'
940 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
941
942 # Set CROS_UI since some behaviors in ui.py depend on the
943 # particular UI in use. TODO(jsalz): Remove this (and all
944 # places it is used) when the GTK UI is removed.
945 os.environ['CROS_UI'] = self.options.ui
946
947 if self.options.ui == 'chrome':
948 self.env.launch_chrome()
949 logging.info('Waiting for a web socket connection')
950 self.web_socket_manager.wait()
951
952 # Wait for the test widget size to be set; this is done in
953 # an asynchronous RPC so there is a small chance that the
954 # web socket might be opened first.
955 for _ in range(100): # 10 s
956 try:
957 if self.state_instance.get_shared_data('test_widget_size'):
958 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800959 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800960 pass # Retry
961 time.sleep(0.1) # 100 ms
962 else:
963 logging.warn('Never received test_widget_size from UI')
964 elif self.options.ui == 'gtk':
965 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800966
Jon Salz0697cbf2012-07-04 15:14:04 +0800967 for handler in self.on_ui_startup:
968 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800969
Jon Salz0697cbf2012-07-04 15:14:04 +0800970 self.prespawner = Prespawner()
971 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800972
Jon Salz0697cbf2012-07-04 15:14:04 +0800973 def state_change_callback(test, test_state):
974 self.event_client.post_event(
975 Event(Event.Type.STATE_CHANGE,
976 path=test.path, state=test_state))
977 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800978
Jon Salz0697cbf2012-07-04 15:14:04 +0800979 try:
980 tests_after_shutdown = self.state_instance.get_shared_data(
981 'tests_after_shutdown')
982 except KeyError:
983 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800984
Jon Salz5c344f62012-07-13 14:31:16 +0800985 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
986 if not force_auto_run and tests_after_shutdown is not None:
Jon Salz0697cbf2012-07-04 15:14:04 +0800987 logging.info('Resuming tests after shutdown: %s',
988 tests_after_shutdown)
Jon Salz0697cbf2012-07-04 15:14:04 +0800989 self.tests_to_run.extend(
990 self.test_list.lookup_path(t) for t in tests_after_shutdown)
991 self.run_queue.put(self.run_next_test)
992 else:
Jon Salz5c344f62012-07-13 14:31:16 +0800993 if force_auto_run or self.test_list.options.auto_run_on_start:
Jon Salz0697cbf2012-07-04 15:14:04 +0800994 self.run_queue.put(
995 lambda: self.run_tests(self.test_list, untested_only=True))
Jon Salz5c344f62012-07-13 14:31:16 +0800996 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800997
Jon Salz0697cbf2012-07-04 15:14:04 +0800998 def run(self):
999 '''Runs Goofy.'''
1000 # Process events forever.
1001 while self.run_once(True):
1002 pass
Jon Salz73e0fd02012-04-04 11:46:38 +08001003
Jon Salz0697cbf2012-07-04 15:14:04 +08001004 def run_once(self, block=False):
1005 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001006
Jon Salz0697cbf2012-07-04 15:14:04 +08001007 Args:
1008 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +08001009
Jon Salz0697cbf2012-07-04 15:14:04 +08001010 Returns:
1011 True to keep going or False to shut down.
1012 '''
1013 events = utils.DrainQueue(self.run_queue)
cychiang21886742012-07-05 15:16:32 +08001014 while not events:
Jon Salz0697cbf2012-07-04 15:14:04 +08001015 # Nothing on the run queue.
1016 self._run_queue_idle()
1017 if block:
1018 # Block for at least one event...
cychiang21886742012-07-05 15:16:32 +08001019 try:
1020 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
1021 except Queue.Empty:
1022 # Keep going (calling _run_queue_idle() again at the top of
1023 # the loop)
1024 continue
Jon Salz0697cbf2012-07-04 15:14:04 +08001025 # ...and grab anything else that showed up at the same
1026 # time.
1027 events.extend(utils.DrainQueue(self.run_queue))
cychiang21886742012-07-05 15:16:32 +08001028 else:
1029 break
Jon Salz51528e12012-07-02 18:54:45 +08001030
Jon Salz0697cbf2012-07-04 15:14:04 +08001031 for event in events:
1032 if not event:
1033 # Shutdown request.
1034 self.run_queue.task_done()
1035 return False
Jon Salz51528e12012-07-02 18:54:45 +08001036
Jon Salz0697cbf2012-07-04 15:14:04 +08001037 try:
1038 event()
Jon Salz85a39882012-07-05 16:45:04 +08001039 except: # pylint: disable=W0702
1040 logging.exception('Error in event loop')
Jon Salz0697cbf2012-07-04 15:14:04 +08001041 self.record_exception(traceback.format_exception_only(
1042 *sys.exc_info()[:2]))
1043 # But keep going
1044 finally:
1045 self.run_queue.task_done()
1046 return True
Jon Salz0405ab52012-03-16 15:26:52 +08001047
Jon Salz8fa8e832012-07-13 19:04:09 +08001048 def sync_time_in_background(self):
1049 '''Attempts to sync time with the shopfloor server.'''
1050 if ((not self.test_list.options.sync_time_period_secs) or
1051 (not self.time_sanitizer) or
1052 self.time_synced or
1053 factory.in_chroot()):
1054 # Not enabled or already succeeded.
1055 return
1056
1057 now = time.time()
1058 if self.last_sync_time and (
1059 now - self.last_sync_time <
1060 self.test_list.options.sync_time_period_secs):
1061 # Not yet time for another check.
1062 return
1063 self.last_sync_time = now
1064
1065 def target():
1066 try:
1067 self.time_sanitizer.SyncWithShopfloor()
1068 self.time_synced = True
1069 except: # pylint: disable=W0702
1070 # Oh well. Log an error (but no trace)
1071 logging.info(
1072 'Unable to get time from shopfloor server: %s',
1073 utils.FormatExceptionOnly())
1074
1075 thread = threading.Thread(target=target)
1076 thread.daemon = True
1077 thread.start()
1078
Jon Salz0697cbf2012-07-04 15:14:04 +08001079 def _run_queue_idle(self):
1080 '''Invoked when the run queue has no events.'''
1081 self.check_connection_manager()
cychiang21886742012-07-05 15:16:32 +08001082 self.check_for_updates()
Jon Salz8fa8e832012-07-13 19:04:09 +08001083 self.sync_time_in_background()
Jon Salz57717ca2012-04-04 16:47:25 +08001084
Jon Salz0697cbf2012-07-04 15:14:04 +08001085 def _handle_event_logs(self, log_name, chunk):
1086 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001087
Jon Salz0697cbf2012-07-04 15:14:04 +08001088 Attempts to upload the event logs to the shopfloor server.
1089 '''
1090 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
1091 start_time = time.time()
1092 logging.info('Syncing %s', description)
1093 shopfloor_client = shopfloor.get_instance(
1094 detect=True,
1095 timeout=self.test_list.options.shopfloor_timeout_secs)
1096 shopfloor_client.UploadEvent(log_name, chunk)
1097 logging.info(
1098 'Successfully synced %s in %.03f s',
1099 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +08001100
Jon Salz0697cbf2012-07-04 15:14:04 +08001101 def run_tests_with_status(self, statuses_to_run, starting_at=None,
1102 root=None):
1103 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001104
Jon Salz0697cbf2012-07-04 15:14:04 +08001105 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001106
Jon Salz0697cbf2012-07-04 15:14:04 +08001107 Args:
1108 starting_at: If provided, only auto-runs tests beginning with
1109 this test.
1110 '''
1111 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +08001112
Jon Salz0697cbf2012-07-04 15:14:04 +08001113 if starting_at:
1114 # Make sure they passed a test, not a string.
1115 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +08001116
Jon Salz0697cbf2012-07-04 15:14:04 +08001117 tests_to_reset = []
1118 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +08001119
Jon Salz0697cbf2012-07-04 15:14:04 +08001120 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +08001121
Jon Salz0697cbf2012-07-04 15:14:04 +08001122 for test in root.get_top_level_tests():
1123 if starting_at:
1124 if test == starting_at:
1125 # We've found starting_at; do auto-run on all
1126 # subsequent tests.
1127 found_starting_at = True
1128 if not found_starting_at:
1129 # Don't start this guy yet
1130 continue
Jon Salz0405ab52012-03-16 15:26:52 +08001131
Jon Salz0697cbf2012-07-04 15:14:04 +08001132 status = test.get_state().status
1133 if status == TestState.ACTIVE or status in statuses_to_run:
1134 # Reset the test (later; we will need to abort
1135 # all active tests first).
1136 tests_to_reset.append(test)
1137 if status in statuses_to_run:
1138 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +08001139
Jon Salz0697cbf2012-07-04 15:14:04 +08001140 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +08001141
Jon Salz0697cbf2012-07-04 15:14:04 +08001142 # Reset all statuses of the tests to run (in case any tests were active;
1143 # we want them to be run again).
1144 for test_to_reset in tests_to_reset:
1145 for test in test_to_reset.walk():
1146 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +08001147
Jon Salz0697cbf2012-07-04 15:14:04 +08001148 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +08001149
Jon Salz0697cbf2012-07-04 15:14:04 +08001150 def restart_tests(self, root=None):
1151 '''Restarts all tests.'''
1152 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001153
Jon Salz0697cbf2012-07-04 15:14:04 +08001154 self.abort_active_tests()
1155 for test in root.walk():
1156 test.update_state(status=TestState.UNTESTED)
1157 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001158
Jon Salz0697cbf2012-07-04 15:14:04 +08001159 def auto_run(self, starting_at=None, root=None):
1160 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001161
Jon Salz0697cbf2012-07-04 15:14:04 +08001162 Args:
1163 starting_at: If provide, only auto-runs tests beginning with
1164 this test.
1165 '''
1166 root = root or self.test_list
1167 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1168 starting_at=starting_at,
1169 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001170
Jon Salz0697cbf2012-07-04 15:14:04 +08001171 def re_run_failed(self, root=None):
1172 '''Re-runs failed tests.'''
1173 root = root or self.test_list
1174 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001175
Jon Salz0697cbf2012-07-04 15:14:04 +08001176 def show_review_information(self):
1177 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001178
Jon Salz0697cbf2012-07-04 15:14:04 +08001179 The information screene is rendered by main UI program (ui.py), so in
1180 goofy we only need to kill all active tests, set them as untested, and
1181 clear remaining tests.
1182 '''
1183 self.kill_active_tests(False)
1184 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001185
Jon Salz0697cbf2012-07-04 15:14:04 +08001186 def handle_switch_test(self, event):
1187 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001188
Jon Salz0697cbf2012-07-04 15:14:04 +08001189 @param event: The SWITCH_TEST event.
1190 '''
1191 test = self.test_list.lookup_path(event.path)
1192 if not test:
1193 logging.error('Unknown test %r', event.key)
1194 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001195
Jon Salz0697cbf2012-07-04 15:14:04 +08001196 invoc = self.invocations.get(test)
1197 if invoc and test.backgroundable:
1198 # Already running: just bring to the front if it
1199 # has a UI.
1200 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001201 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001202 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001203
Jon Salz0697cbf2012-07-04 15:14:04 +08001204 self.abort_active_tests()
1205 for t in test.walk():
1206 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001207
Jon Salz0697cbf2012-07-04 15:14:04 +08001208 if self.test_list.options.auto_run_on_keypress:
1209 self.auto_run(starting_at=test)
1210 else:
1211 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001212
Jon Salz0697cbf2012-07-04 15:14:04 +08001213 def wait(self):
1214 '''Waits for all pending invocations.
1215
1216 Useful for testing.
1217 '''
Jon Salz1acc8742012-07-17 17:45:55 +08001218 while self.invocations:
1219 for k, v in self.invocations.iteritems():
1220 logging.info('Waiting for %s to complete...', k)
1221 v.thread.join()
1222 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001223
1224 def check_exceptions(self):
1225 '''Raises an error if any exceptions have occurred in
1226 invocation threads.'''
1227 if self.exceptions:
1228 raise RuntimeError('Exception in invocation thread: %r' %
1229 self.exceptions)
1230
1231 def record_exception(self, msg):
1232 '''Records an exception in an invocation thread.
1233
1234 An exception with the given message will be rethrown when
1235 Goofy is destroyed.'''
1236 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001237
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001238
1239if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001240 Goofy().main()