blob: a73d982b132c737b0daedcf76aa6dbd427c17704 [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
Jon Salz37eccbd2012-05-25 16:06:52 +08005# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08006# Use of this source code is governed by a BSD-style license that can be
7# found in the LICENSE file.
8
9'''
10The main factory flow that runs the factory test and finalizes a device.
11'''
12
Jon Salz0405ab52012-03-16 15:26:52 +080013import logging
14import os
Jon Salz73e0fd02012-04-04 11:46:38 +080015import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080016import subprocess
17import sys
Jon Salz0405ab52012-03-16 15:26:52 +080018import threading
19import time
20import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080021import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080022from collections import deque
23from optparse import OptionParser
Hung-Te Linf2f78f72012-02-08 19:27:11 +080024
Jon Salz0697cbf2012-07-04 15:14:04 +080025import factory_common # pylint: disable=W0611
Jon Salz83591782012-06-26 11:09:58 +080026from cros.factory.goofy.prespawner import Prespawner
27from cros.factory.test import factory
28from cros.factory.test import state
29from cros.factory.test.factory import TestState
30from cros.factory.goofy import updater
Jon Salz51528e12012-07-02 18:54:45 +080031from cros.factory.goofy import test_steps
32from cros.factory.goofy.event_log_watcher import EventLogWatcher
33from cros.factory.test import shopfloor
Jon Salz83591782012-06-26 11:09:58 +080034from cros.factory.test import utils
35from cros.factory.test.event import Event
36from cros.factory.test.event import EventClient
37from cros.factory.test.event import EventServer
38from cros.factory.event_log import EventLog
39from cros.factory.goofy.invocation import TestInvocation
40from cros.factory.goofy import system
41from cros.factory.goofy import test_environment
42from 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 Salz758e6cc2012-04-03 15:47:07 +080052GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
53You are running Goofy inside the chroot. Autotests are not supported.
54
55To use Goofy in the chroot, first install an Xvnc server:
56
Jon Salz0697cbf2012-07-04 15:14:04 +080057 sudo apt-get install tightvncserver
Jon Salz758e6cc2012-04-03 15:47:07 +080058
59...and then start a VNC X server outside the chroot:
60
Jon Salz0697cbf2012-07-04 15:14:04 +080061 vncserver :10 &
62 vncviewer :10
Jon Salz758e6cc2012-04-03 15:47:07 +080063
64...and run Goofy as follows:
65
Jon Salz0697cbf2012-07-04 15:14:04 +080066 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
Jon Salz758e6cc2012-04-03 15:47:07 +080067''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080068suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080069
70def get_hwid_cfg():
Jon Salz0697cbf2012-07-04 15:14:04 +080071 '''
72 Returns the HWID config tag, or an empty string if none can be found.
73 '''
74 if 'CROS_HWID' in os.environ:
75 return os.environ['CROS_HWID']
76 if os.path.exists(HWID_CFG_PATH):
77 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
78 return hwid_cfg_handle.read().strip()
79 return ''
Hung-Te Linf2f78f72012-02-08 19:27:11 +080080
81
82def find_test_list():
Jon Salz0697cbf2012-07-04 15:14:04 +080083 '''
84 Returns the path to the active test list, based on the HWID config tag.
85 '''
86 hwid_cfg = get_hwid_cfg()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080087
Jon Salz0697cbf2012-07-04 15:14:04 +080088 search_dirs = [CUSTOM_DIR, DEFAULT_TEST_LISTS_DIR]
Jon Salz2f757d42012-06-27 17:06:42 +080089
Jon Salz0697cbf2012-07-04 15:14:04 +080090 # Try in order: test_list_${hwid_cfg}, test_list, test_list.all
91 search_files = ['test_list', 'test_list.all']
92 if hwid_cfg:
93 search_files.insert(0, hwid_cfg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080094
Jon Salz0697cbf2012-07-04 15:14:04 +080095 for d in search_dirs:
96 for f in search_files:
97 test_list = os.path.join(d, f)
98 if os.path.exists(test_list):
99 return test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800100
Jon Salz0697cbf2012-07-04 15:14:04 +0800101 logging.warn('Cannot find test lists named any of %s in any of %s',
102 search_files, search_dirs)
103 return None
Jon Salz73e0fd02012-04-04 11:46:38 +0800104
Jon Salz73e0fd02012-04-04 11:46:38 +0800105_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800106
107class Goofy(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800108 '''
109 The main factory flow.
110
111 Note that all methods in this class must be invoked from the main
112 (event) thread. Other threads, such as callbacks and TestInvocation
113 methods, should instead post events on the run queue.
114
115 TODO: Unit tests. (chrome-os-partner:7409)
116
117 Properties:
118 uuid: A unique UUID for this invocation of Goofy.
119 state_instance: An instance of FactoryState.
120 state_server: The FactoryState XML/RPC server.
121 state_server_thread: A thread running state_server.
122 event_server: The EventServer socket server.
123 event_server_thread: A thread running event_server.
124 event_client: A client to the event server.
125 connection_manager: The connection_manager object.
126 network_enabled: Whether the connection_manager is currently
127 enabling connections.
128 ui_process: The factory ui process object.
129 run_queue: A queue of callbacks to invoke from the main thread.
130 invocations: A map from FactoryTest objects to the corresponding
131 TestInvocations objects representing active tests.
132 tests_to_run: A deque of tests that should be run when the current
133 test(s) complete.
134 options: Command-line options.
135 args: Command-line args.
136 test_list: The test list.
137 event_handlers: Map of Event.Type to the method used to handle that
138 event. If the method has an 'event' argument, the event is passed
139 to the handler.
140 exceptions: Exceptions encountered in invocation threads.
141 '''
142 def __init__(self):
143 self.uuid = str(uuid.uuid4())
144 self.state_instance = None
145 self.state_server = None
146 self.state_server_thread = None
147 self.event_server = None
148 self.event_server_thread = None
149 self.event_client = None
150 self.connection_manager = None
151 self.log_watcher = None
152 self.network_enabled = True
153 self.event_log = None
154 self.prespawner = None
155 self.ui_process = None
156 self.run_queue = Queue.Queue()
157 self.invocations = {}
158 self.tests_to_run = deque()
159 self.visible_test = None
160 self.chrome = None
161
162 self.options = None
163 self.args = None
164 self.test_list = None
165 self.on_ui_startup = []
166 self.env = None
167 self.last_shutdown_time = None
168
169 def test_or_root(event):
170 '''Returns the top-level parent for a test (the root node of the
171 tests that need to be run together if the given test path is to
172 be run).'''
173 try:
174 path = event.path
175 except AttributeError:
176 path = None
177
178 if path:
179 return (self.test_list.lookup_path(path).
180 get_top_level_parent_or_group())
181 else:
182 return self.test_list
183
184 self.event_handlers = {
185 Event.Type.SWITCH_TEST: self.handle_switch_test,
186 Event.Type.SHOW_NEXT_ACTIVE_TEST:
187 lambda event: self.show_next_active_test(),
188 Event.Type.RESTART_TESTS:
189 lambda event: self.restart_tests(root=test_or_root(event)),
190 Event.Type.AUTO_RUN:
191 lambda event: self.auto_run(root=test_or_root(event)),
192 Event.Type.RE_RUN_FAILED:
193 lambda event: self.re_run_failed(root=test_or_root(event)),
194 Event.Type.RUN_TESTS_WITH_STATUS:
195 lambda event: self.run_tests_with_status(
196 event.status,
197 root=test_or_root(event)),
198 Event.Type.REVIEW:
199 lambda event: self.show_review_information(),
200 Event.Type.UPDATE_SYSTEM_INFO:
201 lambda event: self.update_system_info(),
202 Event.Type.UPDATE_FACTORY:
203 lambda event: self.update_factory(),
204 Event.Type.STOP:
205 lambda event: self.stop(),
Jon Salz36fbbb52012-07-05 13:45:06 +0800206 Event.Type.SET_VISIBLE_TEST:
207 lambda event: self.set_visible_test(
208 self.test_list.lookup_path(event.path)),
Jon Salz0697cbf2012-07-04 15:14:04 +0800209 }
210
211 self.exceptions = []
212 self.web_socket_manager = None
213
214 def destroy(self):
215 if self.chrome:
216 self.chrome.kill()
217 self.chrome = None
218 if self.ui_process:
219 utils.kill_process_tree(self.ui_process, 'ui')
220 self.ui_process = None
221 if self.web_socket_manager:
222 logging.info('Stopping web sockets')
223 self.web_socket_manager.close()
224 self.web_socket_manager = None
225 if self.state_server_thread:
226 logging.info('Stopping state server')
227 self.state_server.shutdown()
228 self.state_server_thread.join()
229 self.state_server.server_close()
230 self.state_server_thread = None
231 if self.state_instance:
232 self.state_instance.close()
233 if self.event_server_thread:
234 logging.info('Stopping event server')
235 self.event_server.shutdown() # pylint: disable=E1101
236 self.event_server_thread.join()
237 self.event_server.server_close()
238 self.event_server_thread = None
239 if self.log_watcher:
240 if self.log_watcher.IsThreadStarted():
241 self.log_watcher.StopWatchThread()
242 self.log_watcher = None
243 if self.prespawner:
244 logging.info('Stopping prespawner')
245 self.prespawner.stop()
246 self.prespawner = None
247 if self.event_client:
248 logging.info('Closing event client')
249 self.event_client.close()
250 self.event_client = None
251 if self.event_log:
252 self.event_log.Close()
253 self.event_log = None
254 self.check_exceptions()
255 logging.info('Done destroying Goofy')
256
257 def start_state_server(self):
258 self.state_instance, self.state_server = (
259 state.create_server(bind_address='0.0.0.0'))
260 logging.info('Starting state server')
261 self.state_server_thread = threading.Thread(
262 target=self.state_server.serve_forever,
263 name='StateServer')
264 self.state_server_thread.start()
265
266 def start_event_server(self):
267 self.event_server = EventServer()
268 logging.info('Starting factory event server')
269 self.event_server_thread = threading.Thread(
270 target=self.event_server.serve_forever,
271 name='EventServer') # pylint: disable=E1101
272 self.event_server_thread.start()
273
274 self.event_client = EventClient(
275 callback=self.handle_event, event_loop=self.run_queue)
276
277 self.web_socket_manager = WebSocketManager(self.uuid)
278 self.state_server.add_handler("/event",
279 self.web_socket_manager.handle_web_socket)
280
281 def start_ui(self):
282 ui_proc_args = [
283 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
284 self.options.test_list]
285 if self.options.verbose:
286 ui_proc_args.append('-v')
287 logging.info('Starting ui %s', ui_proc_args)
288 self.ui_process = subprocess.Popen(ui_proc_args)
289 logging.info('Waiting for UI to come up...')
290 self.event_client.wait(
291 lambda event: event.type == Event.Type.UI_READY)
292 logging.info('UI has started')
293
294 def set_visible_test(self, test):
295 if self.visible_test == test:
296 return
297
298 if test:
299 test.update_state(visible=True)
300 if self.visible_test:
301 self.visible_test.update_state(visible=False)
302 self.visible_test = test
303
304 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800305 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800306 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800307
Jon Salz0697cbf2012-07-04 15:14:04 +0800308 @param test: The ShutdownStep.
309 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800310 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800311 test_state = test.update_state(increment_shutdown_count=1)
312 logging.info('Detected shutdown (%d of %d)',
313 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800314
Jon Salz0697cbf2012-07-04 15:14:04 +0800315 def log_and_update_state(status, error_msg, **kw):
316 self.event_log.Log('rebooted',
317 status=status, error_msg=error_msg, **kw)
318 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800319
Jon Salz0697cbf2012-07-04 15:14:04 +0800320 if not self.last_shutdown_time:
321 log_and_update_state(status=TestState.FAILED,
322 error_msg='Unable to read shutdown_time')
323 return
Jon Salz258a40c2012-04-19 12:34:01 +0800324
Jon Salz0697cbf2012-07-04 15:14:04 +0800325 now = time.time()
326 logging.info('%.03f s passed since reboot',
327 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800328
Jon Salz0697cbf2012-07-04 15:14:04 +0800329 if self.last_shutdown_time > now:
330 test.update_state(status=TestState.FAILED,
331 error_msg='Time moved backward during reboot')
332 elif (isinstance(test, factory.RebootStep) and
333 self.test_list.options.max_reboot_time_secs and
334 (now - self.last_shutdown_time >
335 self.test_list.options.max_reboot_time_secs)):
336 # A reboot took too long; fail. (We don't check this for
337 # HaltSteps, because the machine could be halted for a
338 # very long time, and even unplugged with battery backup,
339 # thus hosing the clock.)
340 log_and_update_state(
341 status=TestState.FAILED,
342 error_msg=('More than %d s elapsed during reboot '
343 '(%.03f s, from %s to %s)' % (
344 self.test_list.options.max_reboot_time_secs,
345 now - self.last_shutdown_time,
346 utils.TimeString(self.last_shutdown_time),
347 utils.TimeString(now))),
348 duration=(now-self.last_shutdown_time))
349 elif test_state.shutdown_count == test.iterations:
350 # Good!
351 log_and_update_state(status=TestState.PASSED,
352 duration=(now - self.last_shutdown_time),
353 error_msg='')
354 elif test_state.shutdown_count > test.iterations:
355 # Shut down too many times
356 log_and_update_state(status=TestState.FAILED,
357 error_msg='Too many shutdowns')
358 elif utils.are_shift_keys_depressed():
359 logging.info('Shift keys are depressed; cancelling restarts')
360 # Abort shutdown
361 log_and_update_state(
362 status=TestState.FAILED,
363 error_msg='Shutdown aborted with double shift keys')
364 else:
365 def handler():
366 if self._prompt_cancel_shutdown(
367 test, test_state.shutdown_count + 1):
368 log_and_update_state(
369 status=TestState.FAILED,
370 error_msg='Shutdown aborted by operator')
371 return
Jon Salz0405ab52012-03-16 15:26:52 +0800372
Jon Salz0697cbf2012-07-04 15:14:04 +0800373 # Time to shutdown again
374 log_and_update_state(
375 status=TestState.ACTIVE,
376 error_msg='',
377 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800378
Jon Salz0697cbf2012-07-04 15:14:04 +0800379 self.event_log.Log('shutdown', operation='reboot')
380 self.state_instance.set_shared_data('shutdown_time',
381 time.time())
382 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800383
Jon Salz0697cbf2012-07-04 15:14:04 +0800384 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800385
Jon Salz0697cbf2012-07-04 15:14:04 +0800386 def _prompt_cancel_shutdown(self, test, iteration):
387 if self.options.ui != 'chrome':
388 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800389
Jon Salz0697cbf2012-07-04 15:14:04 +0800390 pending_shutdown_data = {
391 'delay_secs': test.delay_secs,
392 'time': time.time() + test.delay_secs,
393 'operation': test.operation,
394 'iteration': iteration,
395 'iterations': test.iterations,
396 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800397
Jon Salz0697cbf2012-07-04 15:14:04 +0800398 # Create a new (threaded) event client since we
399 # don't want to use the event loop for this.
400 with EventClient() as event_client:
401 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
402 **pending_shutdown_data))
403 aborted = event_client.wait(
404 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
405 timeout=test.delay_secs) is not None
406 if aborted:
407 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
408 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800409
Jon Salz0697cbf2012-07-04 15:14:04 +0800410 def init_states(self):
411 '''
412 Initializes all states on startup.
413 '''
414 for test in self.test_list.get_all_tests():
415 # Make sure the state server knows about all the tests,
416 # defaulting to an untested state.
417 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800418
Jon Salz0697cbf2012-07-04 15:14:04 +0800419 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800420
Jon Salz0697cbf2012-07-04 15:14:04 +0800421 # Any 'active' tests should be marked as failed now.
422 for test in self.test_list.walk():
423 test_state = test.get_state()
424 if test_state.status != TestState.ACTIVE:
425 continue
426 if isinstance(test, factory.ShutdownStep):
427 # Shutdown while the test was active - that's good.
428 self.handle_shutdown_complete(test, test_state)
429 else:
430 # Unexpected shutdown. Grab /var/log/messages for context.
431 if var_log_messages is None:
432 try:
433 var_log_messages = (
434 utils.var_log_messages_before_reboot())
435 # Write it to the log, to make it easier to
436 # correlate with /var/log/messages.
437 logging.info(
438 'Unexpected shutdown. '
439 'Tail of /var/log/messages before last reboot:\n'
440 '%s', ('\n'.join(
441 ' ' + x for x in var_log_messages)))
442 except: # pylint: disable=W0702
443 logging.exception('Unable to grok /var/log/messages')
444 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800445
Jon Salz0697cbf2012-07-04 15:14:04 +0800446 error_msg = 'Unexpected shutdown while test was running'
447 self.event_log.Log('end_test',
448 path=test.path,
449 status=TestState.FAILED,
450 invocation=test.get_state().invocation,
451 error_msg=error_msg,
452 var_log_messages='\n'.join(var_log_messages))
453 test.update_state(
454 status=TestState.FAILED,
455 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800456
Jon Salz0697cbf2012-07-04 15:14:04 +0800457 def show_next_active_test(self):
458 '''
459 Rotates to the next visible active test.
460 '''
461 self.reap_completed_tests()
462 active_tests = [
463 t for t in self.test_list.walk()
464 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
465 if not active_tests:
466 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800467
Jon Salz0697cbf2012-07-04 15:14:04 +0800468 try:
469 next_test = active_tests[
470 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
471 except ValueError: # visible_test not present in active_tests
472 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800473
Jon Salz0697cbf2012-07-04 15:14:04 +0800474 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800475
Jon Salz0697cbf2012-07-04 15:14:04 +0800476 def handle_event(self, event):
477 '''
478 Handles an event from the event server.
479 '''
480 handler = self.event_handlers.get(event.type)
481 if handler:
482 handler(event)
483 else:
484 # We don't register handlers for all event types - just ignore
485 # this event.
486 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800487
Jon Salz0697cbf2012-07-04 15:14:04 +0800488 def run_next_test(self):
489 '''
490 Runs the next eligible test (or tests) in self.tests_to_run.
491 '''
492 self.reap_completed_tests()
493 while self.tests_to_run:
494 logging.debug('Tests to run: %s',
495 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800496
Jon Salz0697cbf2012-07-04 15:14:04 +0800497 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800498
Jon Salz0697cbf2012-07-04 15:14:04 +0800499 if test in self.invocations:
500 logging.info('Next test %s is already running', test.path)
501 self.tests_to_run.popleft()
502 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800503
Jon Salz0697cbf2012-07-04 15:14:04 +0800504 if self.invocations and not (test.backgroundable and all(
505 [x.backgroundable for x in self.invocations])):
506 logging.debug('Waiting for non-backgroundable tests to '
507 'complete before running %s', test.path)
508 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800509
Jon Salz0697cbf2012-07-04 15:14:04 +0800510 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800511
Jon Salz0697cbf2012-07-04 15:14:04 +0800512 if isinstance(test, factory.ShutdownStep):
513 if os.path.exists(NO_REBOOT_FILE):
514 test.update_state(
515 status=TestState.FAILED, increment_count=1,
516 error_msg=('Skipped shutdown since %s is present' %
517 NO_REBOOT_FILE))
518 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800519
Jon Salz0697cbf2012-07-04 15:14:04 +0800520 test.update_state(status=TestState.ACTIVE, increment_count=1,
521 error_msg='', shutdown_count=0)
522 if self._prompt_cancel_shutdown(test, 1):
523 self.event_log.Log('reboot_cancelled')
524 test.update_state(
525 status=TestState.FAILED, increment_count=1,
526 error_msg='Shutdown aborted by operator',
527 shutdown_count=0)
528 return
Jon Salz2f757d42012-06-27 17:06:42 +0800529
Jon Salz0697cbf2012-07-04 15:14:04 +0800530 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800531 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800532 'tests_after_shutdown',
533 [t.path for t in self.tests_to_run])
534 # Save shutdown time
535 self.state_instance.set_shared_data('shutdown_time',
536 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800537
Jon Salz0697cbf2012-07-04 15:14:04 +0800538 with self.env.lock:
539 self.event_log.Log('shutdown', operation=test.operation)
540 shutdown_result = self.env.shutdown(test.operation)
541 if shutdown_result:
542 # That's all, folks!
543 self.run_queue.put(None)
544 return
545 else:
546 # Just pass (e.g., in the chroot).
547 test.update_state(status=TestState.PASSED)
548 self.state_instance.set_shared_data(
549 'tests_after_shutdown', None)
550 # Send event with no fields to indicate that there is no
551 # longer a pending shutdown.
552 self.event_client.post_event(Event(
553 Event.Type.PENDING_SHUTDOWN))
554 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800555
Jon Salz0697cbf2012-07-04 15:14:04 +0800556 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
557 self.invocations[test] = invoc
558 if self.visible_test is None and test.has_ui:
559 self.set_visible_test(test)
560 self.check_connection_manager()
561 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800562
Jon Salz0697cbf2012-07-04 15:14:04 +0800563 def check_connection_manager(self):
564 exclusive_tests = [
565 test.path
566 for test in self.invocations
567 if test.is_exclusive(
568 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
569 if exclusive_tests:
570 # Make sure networking is disabled.
571 if self.network_enabled:
572 logging.info('Disabling network, as requested by %s',
573 exclusive_tests)
574 self.connection_manager.DisableNetworking()
575 self.network_enabled = False
576 else:
577 # Make sure networking is enabled.
578 if not self.network_enabled:
579 logging.info('Re-enabling network')
580 self.connection_manager.EnableNetworking()
581 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800582
Jon Salz0697cbf2012-07-04 15:14:04 +0800583 def run_tests(self, subtrees, untested_only=False):
584 '''
585 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800586
Jon Salz0697cbf2012-07-04 15:14:04 +0800587 The tests are run in order unless one fails (then stops).
588 Backgroundable tests are run simultaneously; when a foreground test is
589 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800590
Jon Salz0697cbf2012-07-04 15:14:04 +0800591 @param subtrees: Node or nodes containing tests to run (may either be
592 a single test or a list). Duplicates will be ignored.
593 '''
594 if type(subtrees) != list:
595 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800596
Jon Salz0697cbf2012-07-04 15:14:04 +0800597 # Nodes we've seen so far, to avoid duplicates.
598 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800599
Jon Salz0697cbf2012-07-04 15:14:04 +0800600 self.tests_to_run = deque()
601 for subtree in subtrees:
602 for test in subtree.walk():
603 if test in seen:
604 continue
605 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800606
Jon Salz0697cbf2012-07-04 15:14:04 +0800607 if not test.is_leaf():
608 continue
609 if (untested_only and
610 test.get_state().status != TestState.UNTESTED):
611 continue
612 self.tests_to_run.append(test)
613 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800614
Jon Salz0697cbf2012-07-04 15:14:04 +0800615 def reap_completed_tests(self):
616 '''
617 Removes completed tests from the set of active tests.
618
619 Also updates the visible test if it was reaped.
620 '''
621 for t, v in dict(self.invocations).iteritems():
622 if v.is_completed():
623 del self.invocations[t]
624
625 if (self.visible_test is None or
626 self.visible_test not in self.invocations):
627 self.set_visible_test(None)
628 # Make the first running test, if any, the visible test
629 for t in self.test_list.walk():
630 if t in self.invocations:
631 self.set_visible_test(t)
632 break
633
634 def kill_active_tests(self, abort):
635 '''
636 Kills and waits for all active tests.
637
638 @param abort: True to change state of killed tests to FAILED, False for
639 UNTESTED.
640 '''
641 self.reap_completed_tests()
642 for test, invoc in self.invocations.items():
643 factory.console.info('Killing active test %s...' % test.path)
644 invoc.abort_and_join()
645 factory.console.info('Killed %s' % test.path)
646 del self.invocations[test]
647 if not abort:
648 test.update_state(status=TestState.UNTESTED)
649 self.reap_completed_tests()
650
651 def stop(self):
652 self.kill_active_tests(False)
653 self.run_tests([])
654
655 def abort_active_tests(self):
656 self.kill_active_tests(True)
657
658 def main(self):
659 try:
660 self.init()
661 self.event_log.Log('goofy_init',
662 success=True)
663 except:
664 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800665 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800666 self.event_log.Log('goofy_init',
667 success=False,
668 trace=traceback.format_exc())
669 except: # pylint: disable=W0702
670 pass
671 raise
672
673 self.run()
674
675 def update_system_info(self):
676 '''Updates system info.'''
677 system_info = system.SystemInfo()
678 self.state_instance.set_shared_data('system_info', system_info.__dict__)
679 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
680 system_info=system_info.__dict__))
681 logging.info('System info: %r', system_info.__dict__)
682
683 def update_factory(self):
684 self.kill_active_tests(False)
685 self.run_tests([])
686
687 try:
688 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
689 self.env.shutdown('reboot')
690 except: # pylint: disable=W0702
691 factory.console.exception('Unable to update')
692
693 def init(self, args=None, env=None):
694 '''Initializes Goofy.
695
696 Args:
697 args: A list of command-line arguments. Uses sys.argv if
698 args is None.
699 env: An Environment instance to use (or None to choose
700 FakeChrootEnvironment or DUTEnvironment as appropriate).
701 '''
702 parser = OptionParser()
703 parser.add_option('-v', '--verbose', dest='verbose',
704 action='store_true',
705 help='Enable debug logging')
706 parser.add_option('--print_test_list', dest='print_test_list',
707 metavar='FILE',
708 help='Read and print test list FILE, and exit')
709 parser.add_option('--restart', dest='restart',
710 action='store_true',
711 help='Clear all test state')
712 parser.add_option('--ui', dest='ui', type='choice',
713 choices=['none', 'gtk', 'chrome'],
714 default='gtk',
715 help='UI to use')
716 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
717 type='int', default=1,
718 help=('Factor by which to scale UI '
719 '(Chrome UI only)'))
720 parser.add_option('--test_list', dest='test_list',
721 metavar='FILE',
722 help='Use FILE as test list')
723 (self.options, self.args) = parser.parse_args(args)
724
Jon Salz46b89562012-07-05 11:49:22 +0800725 # Make sure factory directories exist.
726 factory.get_log_root()
727 factory.get_state_root()
728 factory.get_test_data_root()
729
Jon Salz0697cbf2012-07-04 15:14:04 +0800730 global _inited_logging # pylint: disable=W0603
731 if not _inited_logging:
732 factory.init_logging('goofy', verbose=self.options.verbose)
733 _inited_logging = True
734 self.event_log = EventLog('goofy')
735
736 if (not suppress_chroot_warning and
737 factory.in_chroot() and
738 self.options.ui == 'gtk' and
739 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
740 # That's not going to work! Tell the user how to run
741 # this way.
742 logging.warn(GOOFY_IN_CHROOT_WARNING)
743 time.sleep(1)
744
745 if env:
746 self.env = env
747 elif factory.in_chroot():
748 self.env = test_environment.FakeChrootEnvironment()
749 logging.warn(
750 'Using chroot environment: will not actually run autotests')
751 else:
752 self.env = test_environment.DUTEnvironment()
753 self.env.goofy = self
754
755 if self.options.restart:
756 state.clear_state()
757
758 if self.options.print_test_list:
759 print (factory.read_test_list(
760 self.options.print_test_list,
761 test_classes=dict(test_steps.__dict__)).
762 __repr__(recursive=True))
763 return
764
765 if self.options.ui_scale_factor != 1 and utils.in_qemu():
766 logging.warn(
767 'In QEMU; ignoring ui_scale_factor argument')
768 self.options.ui_scale_factor = 1
769
770 logging.info('Started')
771
772 self.start_state_server()
773 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
774 self.state_instance.set_shared_data('ui_scale_factor',
775 self.options.ui_scale_factor)
776 self.last_shutdown_time = (
777 self.state_instance.get_shared_data('shutdown_time', optional=True))
778 self.state_instance.del_shared_data('shutdown_time', optional=True)
779
780 if not self.options.test_list:
781 self.options.test_list = find_test_list()
782 if not self.options.test_list:
783 logging.error('No test list. Aborting.')
784 sys.exit(1)
785 logging.info('Using test list %s', self.options.test_list)
786
787 self.test_list = factory.read_test_list(
788 self.options.test_list,
789 self.state_instance,
790 test_classes=dict(test_steps.__dict__))
791 if not self.state_instance.has_shared_data('ui_lang'):
792 self.state_instance.set_shared_data('ui_lang',
793 self.test_list.options.ui_lang)
794 self.state_instance.set_shared_data(
795 'test_list_options',
796 self.test_list.options.__dict__)
797 self.state_instance.test_list = self.test_list
798
799 self.init_states()
800 self.start_event_server()
801 self.connection_manager = self.env.create_connection_manager(
802 self.test_list.options.wlans)
803 # Note that we create a log watcher even if
804 # sync_event_log_period_secs isn't set (no background
805 # syncing), since we may use it to flush event logs as well.
806 self.log_watcher = EventLogWatcher(
807 self.test_list.options.sync_event_log_period_secs,
808 handle_event_logs_callback=self._handle_event_logs)
809 if self.test_list.options.sync_event_log_period_secs:
810 self.log_watcher.StartWatchThread()
811
812 self.update_system_info()
813
814 os.environ['CROS_FACTORY'] = '1'
815 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
816
817 # Set CROS_UI since some behaviors in ui.py depend on the
818 # particular UI in use. TODO(jsalz): Remove this (and all
819 # places it is used) when the GTK UI is removed.
820 os.environ['CROS_UI'] = self.options.ui
821
822 if self.options.ui == 'chrome':
823 self.env.launch_chrome()
824 logging.info('Waiting for a web socket connection')
825 self.web_socket_manager.wait()
826
827 # Wait for the test widget size to be set; this is done in
828 # an asynchronous RPC so there is a small chance that the
829 # web socket might be opened first.
830 for _ in range(100): # 10 s
831 try:
832 if self.state_instance.get_shared_data('test_widget_size'):
833 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800834 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800835 pass # Retry
836 time.sleep(0.1) # 100 ms
837 else:
838 logging.warn('Never received test_widget_size from UI')
839 elif self.options.ui == 'gtk':
840 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800841
Jon Salz0697cbf2012-07-04 15:14:04 +0800842 for handler in self.on_ui_startup:
843 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800844
Jon Salz0697cbf2012-07-04 15:14:04 +0800845 self.prespawner = Prespawner()
846 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800847
Jon Salz0697cbf2012-07-04 15:14:04 +0800848 def state_change_callback(test, test_state):
849 self.event_client.post_event(
850 Event(Event.Type.STATE_CHANGE,
851 path=test.path, state=test_state))
852 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800853
Jon Salz0697cbf2012-07-04 15:14:04 +0800854 try:
855 tests_after_shutdown = self.state_instance.get_shared_data(
856 'tests_after_shutdown')
857 except KeyError:
858 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800859
Jon Salz0697cbf2012-07-04 15:14:04 +0800860 if tests_after_shutdown is not None:
861 logging.info('Resuming tests after shutdown: %s',
862 tests_after_shutdown)
863 self.state_instance.set_shared_data('tests_after_shutdown', None)
864 self.tests_to_run.extend(
865 self.test_list.lookup_path(t) for t in tests_after_shutdown)
866 self.run_queue.put(self.run_next_test)
867 else:
868 if self.test_list.options.auto_run_on_start:
869 self.run_queue.put(
870 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800871
Jon Salz0697cbf2012-07-04 15:14:04 +0800872 def run(self):
873 '''Runs Goofy.'''
874 # Process events forever.
875 while self.run_once(True):
876 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800877
Jon Salz0697cbf2012-07-04 15:14:04 +0800878 def run_once(self, block=False):
879 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800880
Jon Salz0697cbf2012-07-04 15:14:04 +0800881 Args:
882 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800883
Jon Salz0697cbf2012-07-04 15:14:04 +0800884 Returns:
885 True to keep going or False to shut down.
886 '''
887 events = utils.DrainQueue(self.run_queue)
888 if not events:
889 # Nothing on the run queue.
890 self._run_queue_idle()
891 if block:
892 # Block for at least one event...
893 events.append(self.run_queue.get())
894 # ...and grab anything else that showed up at the same
895 # time.
896 events.extend(utils.DrainQueue(self.run_queue))
Jon Salz51528e12012-07-02 18:54:45 +0800897
Jon Salz0697cbf2012-07-04 15:14:04 +0800898 for event in events:
899 if not event:
900 # Shutdown request.
901 self.run_queue.task_done()
902 return False
Jon Salz51528e12012-07-02 18:54:45 +0800903
Jon Salz0697cbf2012-07-04 15:14:04 +0800904 try:
905 event()
906 except Exception as e: # pylint: disable=W0703
907 logging.error('Error in event loop: %s', e)
908 traceback.print_exc(sys.stderr)
909 self.record_exception(traceback.format_exception_only(
910 *sys.exc_info()[:2]))
911 # But keep going
912 finally:
913 self.run_queue.task_done()
914 return True
Jon Salz0405ab52012-03-16 15:26:52 +0800915
Jon Salz0697cbf2012-07-04 15:14:04 +0800916 def _run_queue_idle(self):
917 '''Invoked when the run queue has no events.'''
918 self.check_connection_manager()
Jon Salz57717ca2012-04-04 16:47:25 +0800919
Jon Salz0697cbf2012-07-04 15:14:04 +0800920 def _handle_event_logs(self, log_name, chunk):
921 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +0800922
Jon Salz0697cbf2012-07-04 15:14:04 +0800923 Attempts to upload the event logs to the shopfloor server.
924 '''
925 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
926 start_time = time.time()
927 logging.info('Syncing %s', description)
928 shopfloor_client = shopfloor.get_instance(
929 detect=True,
930 timeout=self.test_list.options.shopfloor_timeout_secs)
931 shopfloor_client.UploadEvent(log_name, chunk)
932 logging.info(
933 'Successfully synced %s in %.03f s',
934 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +0800935
Jon Salz0697cbf2012-07-04 15:14:04 +0800936 def run_tests_with_status(self, statuses_to_run, starting_at=None,
937 root=None):
938 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +0800939
Jon Salz0697cbf2012-07-04 15:14:04 +0800940 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800941
Jon Salz0697cbf2012-07-04 15:14:04 +0800942 Args:
943 starting_at: If provided, only auto-runs tests beginning with
944 this test.
945 '''
946 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800947
Jon Salz0697cbf2012-07-04 15:14:04 +0800948 if starting_at:
949 # Make sure they passed a test, not a string.
950 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +0800951
Jon Salz0697cbf2012-07-04 15:14:04 +0800952 tests_to_reset = []
953 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +0800954
Jon Salz0697cbf2012-07-04 15:14:04 +0800955 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +0800956
Jon Salz0697cbf2012-07-04 15:14:04 +0800957 for test in root.get_top_level_tests():
958 if starting_at:
959 if test == starting_at:
960 # We've found starting_at; do auto-run on all
961 # subsequent tests.
962 found_starting_at = True
963 if not found_starting_at:
964 # Don't start this guy yet
965 continue
Jon Salz0405ab52012-03-16 15:26:52 +0800966
Jon Salz0697cbf2012-07-04 15:14:04 +0800967 status = test.get_state().status
968 if status == TestState.ACTIVE or status in statuses_to_run:
969 # Reset the test (later; we will need to abort
970 # all active tests first).
971 tests_to_reset.append(test)
972 if status in statuses_to_run:
973 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800974
Jon Salz0697cbf2012-07-04 15:14:04 +0800975 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +0800976
Jon Salz0697cbf2012-07-04 15:14:04 +0800977 # Reset all statuses of the tests to run (in case any tests were active;
978 # we want them to be run again).
979 for test_to_reset in tests_to_reset:
980 for test in test_to_reset.walk():
981 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +0800982
Jon Salz0697cbf2012-07-04 15:14:04 +0800983 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +0800984
Jon Salz0697cbf2012-07-04 15:14:04 +0800985 def restart_tests(self, root=None):
986 '''Restarts all tests.'''
987 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800988
Jon Salz0697cbf2012-07-04 15:14:04 +0800989 self.abort_active_tests()
990 for test in root.walk():
991 test.update_state(status=TestState.UNTESTED)
992 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +0800993
Jon Salz0697cbf2012-07-04 15:14:04 +0800994 def auto_run(self, starting_at=None, root=None):
995 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +0800996
Jon Salz0697cbf2012-07-04 15:14:04 +0800997 Args:
998 starting_at: If provide, only auto-runs tests beginning with
999 this test.
1000 '''
1001 root = root or self.test_list
1002 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1003 starting_at=starting_at,
1004 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001005
Jon Salz0697cbf2012-07-04 15:14:04 +08001006 def re_run_failed(self, root=None):
1007 '''Re-runs failed tests.'''
1008 root = root or self.test_list
1009 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001010
Jon Salz0697cbf2012-07-04 15:14:04 +08001011 def show_review_information(self):
1012 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001013
Jon Salz0697cbf2012-07-04 15:14:04 +08001014 The information screene is rendered by main UI program (ui.py), so in
1015 goofy we only need to kill all active tests, set them as untested, and
1016 clear remaining tests.
1017 '''
1018 self.kill_active_tests(False)
1019 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001020
Jon Salz0697cbf2012-07-04 15:14:04 +08001021 def handle_switch_test(self, event):
1022 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001023
Jon Salz0697cbf2012-07-04 15:14:04 +08001024 @param event: The SWITCH_TEST event.
1025 '''
1026 test = self.test_list.lookup_path(event.path)
1027 if not test:
1028 logging.error('Unknown test %r', event.key)
1029 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001030
Jon Salz0697cbf2012-07-04 15:14:04 +08001031 invoc = self.invocations.get(test)
1032 if invoc and test.backgroundable:
1033 # Already running: just bring to the front if it
1034 # has a UI.
1035 logging.info('Setting visible test to %s', test.path)
Jon Salz36fbbb52012-07-05 13:45:06 +08001036 self.set_visible_test(test)
Jon Salz0697cbf2012-07-04 15:14:04 +08001037 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001038
Jon Salz0697cbf2012-07-04 15:14:04 +08001039 self.abort_active_tests()
1040 for t in test.walk():
1041 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001042
Jon Salz0697cbf2012-07-04 15:14:04 +08001043 if self.test_list.options.auto_run_on_keypress:
1044 self.auto_run(starting_at=test)
1045 else:
1046 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001047
Jon Salz0697cbf2012-07-04 15:14:04 +08001048 def wait(self):
1049 '''Waits for all pending invocations.
1050
1051 Useful for testing.
1052 '''
1053 for k, v in self.invocations.iteritems():
1054 logging.info('Waiting for %s to complete...', k)
1055 v.thread.join()
1056
1057 def check_exceptions(self):
1058 '''Raises an error if any exceptions have occurred in
1059 invocation threads.'''
1060 if self.exceptions:
1061 raise RuntimeError('Exception in invocation thread: %r' %
1062 self.exceptions)
1063
1064 def record_exception(self, msg):
1065 '''Records an exception in an invocation thread.
1066
1067 An exception with the given message will be rethrown when
1068 Goofy is destroyed.'''
1069 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001070
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001071
1072if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001073 Goofy().main()