blob: 35644e898fa8e4ed66e9a63de85551b357c58766 [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(),
206 }
207
208 self.exceptions = []
209 self.web_socket_manager = None
210
211 def destroy(self):
212 if self.chrome:
213 self.chrome.kill()
214 self.chrome = None
215 if self.ui_process:
216 utils.kill_process_tree(self.ui_process, 'ui')
217 self.ui_process = None
218 if self.web_socket_manager:
219 logging.info('Stopping web sockets')
220 self.web_socket_manager.close()
221 self.web_socket_manager = None
222 if self.state_server_thread:
223 logging.info('Stopping state server')
224 self.state_server.shutdown()
225 self.state_server_thread.join()
226 self.state_server.server_close()
227 self.state_server_thread = None
228 if self.state_instance:
229 self.state_instance.close()
230 if self.event_server_thread:
231 logging.info('Stopping event server')
232 self.event_server.shutdown() # pylint: disable=E1101
233 self.event_server_thread.join()
234 self.event_server.server_close()
235 self.event_server_thread = None
236 if self.log_watcher:
237 if self.log_watcher.IsThreadStarted():
238 self.log_watcher.StopWatchThread()
239 self.log_watcher = None
240 if self.prespawner:
241 logging.info('Stopping prespawner')
242 self.prespawner.stop()
243 self.prespawner = None
244 if self.event_client:
245 logging.info('Closing event client')
246 self.event_client.close()
247 self.event_client = None
248 if self.event_log:
249 self.event_log.Close()
250 self.event_log = None
251 self.check_exceptions()
252 logging.info('Done destroying Goofy')
253
254 def start_state_server(self):
255 self.state_instance, self.state_server = (
256 state.create_server(bind_address='0.0.0.0'))
257 logging.info('Starting state server')
258 self.state_server_thread = threading.Thread(
259 target=self.state_server.serve_forever,
260 name='StateServer')
261 self.state_server_thread.start()
262
263 def start_event_server(self):
264 self.event_server = EventServer()
265 logging.info('Starting factory event server')
266 self.event_server_thread = threading.Thread(
267 target=self.event_server.serve_forever,
268 name='EventServer') # pylint: disable=E1101
269 self.event_server_thread.start()
270
271 self.event_client = EventClient(
272 callback=self.handle_event, event_loop=self.run_queue)
273
274 self.web_socket_manager = WebSocketManager(self.uuid)
275 self.state_server.add_handler("/event",
276 self.web_socket_manager.handle_web_socket)
277
278 def start_ui(self):
279 ui_proc_args = [
280 os.path.join(factory.FACTORY_PACKAGE_PATH, 'test', 'ui.py'),
281 self.options.test_list]
282 if self.options.verbose:
283 ui_proc_args.append('-v')
284 logging.info('Starting ui %s', ui_proc_args)
285 self.ui_process = subprocess.Popen(ui_proc_args)
286 logging.info('Waiting for UI to come up...')
287 self.event_client.wait(
288 lambda event: event.type == Event.Type.UI_READY)
289 logging.info('UI has started')
290
291 def set_visible_test(self, test):
292 if self.visible_test == test:
293 return
294
295 if test:
296 test.update_state(visible=True)
297 if self.visible_test:
298 self.visible_test.update_state(visible=False)
299 self.visible_test = test
300
301 def handle_shutdown_complete(self, test, test_state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800302 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800303 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800304
Jon Salz0697cbf2012-07-04 15:14:04 +0800305 @param test: The ShutdownStep.
306 @param test_state: The test state.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800307 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800308 test_state = test.update_state(increment_shutdown_count=1)
309 logging.info('Detected shutdown (%d of %d)',
310 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800311
Jon Salz0697cbf2012-07-04 15:14:04 +0800312 def log_and_update_state(status, error_msg, **kw):
313 self.event_log.Log('rebooted',
314 status=status, error_msg=error_msg, **kw)
315 test.update_state(status=status, error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800316
Jon Salz0697cbf2012-07-04 15:14:04 +0800317 if not self.last_shutdown_time:
318 log_and_update_state(status=TestState.FAILED,
319 error_msg='Unable to read shutdown_time')
320 return
Jon Salz258a40c2012-04-19 12:34:01 +0800321
Jon Salz0697cbf2012-07-04 15:14:04 +0800322 now = time.time()
323 logging.info('%.03f s passed since reboot',
324 now - self.last_shutdown_time)
Jon Salz258a40c2012-04-19 12:34:01 +0800325
Jon Salz0697cbf2012-07-04 15:14:04 +0800326 if self.last_shutdown_time > now:
327 test.update_state(status=TestState.FAILED,
328 error_msg='Time moved backward during reboot')
329 elif (isinstance(test, factory.RebootStep) and
330 self.test_list.options.max_reboot_time_secs and
331 (now - self.last_shutdown_time >
332 self.test_list.options.max_reboot_time_secs)):
333 # A reboot took too long; fail. (We don't check this for
334 # HaltSteps, because the machine could be halted for a
335 # very long time, and even unplugged with battery backup,
336 # thus hosing the clock.)
337 log_and_update_state(
338 status=TestState.FAILED,
339 error_msg=('More than %d s elapsed during reboot '
340 '(%.03f s, from %s to %s)' % (
341 self.test_list.options.max_reboot_time_secs,
342 now - self.last_shutdown_time,
343 utils.TimeString(self.last_shutdown_time),
344 utils.TimeString(now))),
345 duration=(now-self.last_shutdown_time))
346 elif test_state.shutdown_count == test.iterations:
347 # Good!
348 log_and_update_state(status=TestState.PASSED,
349 duration=(now - self.last_shutdown_time),
350 error_msg='')
351 elif test_state.shutdown_count > test.iterations:
352 # Shut down too many times
353 log_and_update_state(status=TestState.FAILED,
354 error_msg='Too many shutdowns')
355 elif utils.are_shift_keys_depressed():
356 logging.info('Shift keys are depressed; cancelling restarts')
357 # Abort shutdown
358 log_and_update_state(
359 status=TestState.FAILED,
360 error_msg='Shutdown aborted with double shift keys')
361 else:
362 def handler():
363 if self._prompt_cancel_shutdown(
364 test, test_state.shutdown_count + 1):
365 log_and_update_state(
366 status=TestState.FAILED,
367 error_msg='Shutdown aborted by operator')
368 return
Jon Salz0405ab52012-03-16 15:26:52 +0800369
Jon Salz0697cbf2012-07-04 15:14:04 +0800370 # Time to shutdown again
371 log_and_update_state(
372 status=TestState.ACTIVE,
373 error_msg='',
374 iteration=test_state.shutdown_count)
Jon Salz73e0fd02012-04-04 11:46:38 +0800375
Jon Salz0697cbf2012-07-04 15:14:04 +0800376 self.event_log.Log('shutdown', operation='reboot')
377 self.state_instance.set_shared_data('shutdown_time',
378 time.time())
379 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800380
Jon Salz0697cbf2012-07-04 15:14:04 +0800381 self.on_ui_startup.append(handler)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800382
Jon Salz0697cbf2012-07-04 15:14:04 +0800383 def _prompt_cancel_shutdown(self, test, iteration):
384 if self.options.ui != 'chrome':
385 return False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800386
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 pending_shutdown_data = {
388 'delay_secs': test.delay_secs,
389 'time': time.time() + test.delay_secs,
390 'operation': test.operation,
391 'iteration': iteration,
392 'iterations': test.iterations,
393 }
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800394
Jon Salz0697cbf2012-07-04 15:14:04 +0800395 # Create a new (threaded) event client since we
396 # don't want to use the event loop for this.
397 with EventClient() as event_client:
398 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN,
399 **pending_shutdown_data))
400 aborted = event_client.wait(
401 lambda event: event.type == Event.Type.CANCEL_SHUTDOWN,
402 timeout=test.delay_secs) is not None
403 if aborted:
404 event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
405 return aborted
Jon Salz258a40c2012-04-19 12:34:01 +0800406
Jon Salz0697cbf2012-07-04 15:14:04 +0800407 def init_states(self):
408 '''
409 Initializes all states on startup.
410 '''
411 for test in self.test_list.get_all_tests():
412 # Make sure the state server knows about all the tests,
413 # defaulting to an untested state.
414 test.update_state(update_parent=False, visible=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415
Jon Salz0697cbf2012-07-04 15:14:04 +0800416 var_log_messages = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800417
Jon Salz0697cbf2012-07-04 15:14:04 +0800418 # Any 'active' tests should be marked as failed now.
419 for test in self.test_list.walk():
420 test_state = test.get_state()
421 if test_state.status != TestState.ACTIVE:
422 continue
423 if isinstance(test, factory.ShutdownStep):
424 # Shutdown while the test was active - that's good.
425 self.handle_shutdown_complete(test, test_state)
426 else:
427 # Unexpected shutdown. Grab /var/log/messages for context.
428 if var_log_messages is None:
429 try:
430 var_log_messages = (
431 utils.var_log_messages_before_reboot())
432 # Write it to the log, to make it easier to
433 # correlate with /var/log/messages.
434 logging.info(
435 'Unexpected shutdown. '
436 'Tail of /var/log/messages before last reboot:\n'
437 '%s', ('\n'.join(
438 ' ' + x for x in var_log_messages)))
439 except: # pylint: disable=W0702
440 logging.exception('Unable to grok /var/log/messages')
441 var_log_messages = []
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800442
Jon Salz0697cbf2012-07-04 15:14:04 +0800443 error_msg = 'Unexpected shutdown while test was running'
444 self.event_log.Log('end_test',
445 path=test.path,
446 status=TestState.FAILED,
447 invocation=test.get_state().invocation,
448 error_msg=error_msg,
449 var_log_messages='\n'.join(var_log_messages))
450 test.update_state(
451 status=TestState.FAILED,
452 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800453
Jon Salz0697cbf2012-07-04 15:14:04 +0800454 def show_next_active_test(self):
455 '''
456 Rotates to the next visible active test.
457 '''
458 self.reap_completed_tests()
459 active_tests = [
460 t for t in self.test_list.walk()
461 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
462 if not active_tests:
463 return
Jon Salz4f6c7172012-06-11 20:45:36 +0800464
Jon Salz0697cbf2012-07-04 15:14:04 +0800465 try:
466 next_test = active_tests[
467 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
468 except ValueError: # visible_test not present in active_tests
469 next_test = active_tests[0]
Jon Salz4f6c7172012-06-11 20:45:36 +0800470
Jon Salz0697cbf2012-07-04 15:14:04 +0800471 self.set_visible_test(next_test)
Jon Salz4f6c7172012-06-11 20:45:36 +0800472
Jon Salz0697cbf2012-07-04 15:14:04 +0800473 def handle_event(self, event):
474 '''
475 Handles an event from the event server.
476 '''
477 handler = self.event_handlers.get(event.type)
478 if handler:
479 handler(event)
480 else:
481 # We don't register handlers for all event types - just ignore
482 # this event.
483 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800484
Jon Salz0697cbf2012-07-04 15:14:04 +0800485 def run_next_test(self):
486 '''
487 Runs the next eligible test (or tests) in self.tests_to_run.
488 '''
489 self.reap_completed_tests()
490 while self.tests_to_run:
491 logging.debug('Tests to run: %s',
492 [x.path for x in self.tests_to_run])
Jon Salz94eb56f2012-06-12 18:01:12 +0800493
Jon Salz0697cbf2012-07-04 15:14:04 +0800494 test = self.tests_to_run[0]
Jon Salz94eb56f2012-06-12 18:01:12 +0800495
Jon Salz0697cbf2012-07-04 15:14:04 +0800496 if test in self.invocations:
497 logging.info('Next test %s is already running', test.path)
498 self.tests_to_run.popleft()
499 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800500
Jon Salz0697cbf2012-07-04 15:14:04 +0800501 if self.invocations and not (test.backgroundable and all(
502 [x.backgroundable for x in self.invocations])):
503 logging.debug('Waiting for non-backgroundable tests to '
504 'complete before running %s', test.path)
505 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800506
Jon Salz0697cbf2012-07-04 15:14:04 +0800507 self.tests_to_run.popleft()
Jon Salz94eb56f2012-06-12 18:01:12 +0800508
Jon Salz0697cbf2012-07-04 15:14:04 +0800509 if isinstance(test, factory.ShutdownStep):
510 if os.path.exists(NO_REBOOT_FILE):
511 test.update_state(
512 status=TestState.FAILED, increment_count=1,
513 error_msg=('Skipped shutdown since %s is present' %
514 NO_REBOOT_FILE))
515 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800516
Jon Salz0697cbf2012-07-04 15:14:04 +0800517 test.update_state(status=TestState.ACTIVE, increment_count=1,
518 error_msg='', shutdown_count=0)
519 if self._prompt_cancel_shutdown(test, 1):
520 self.event_log.Log('reboot_cancelled')
521 test.update_state(
522 status=TestState.FAILED, increment_count=1,
523 error_msg='Shutdown aborted by operator',
524 shutdown_count=0)
525 return
Jon Salz2f757d42012-06-27 17:06:42 +0800526
Jon Salz0697cbf2012-07-04 15:14:04 +0800527 # Save pending test list in the state server
Jon Salzdbf398f2012-06-14 17:30:01 +0800528 self.state_instance.set_shared_data(
Jon Salz0697cbf2012-07-04 15:14:04 +0800529 'tests_after_shutdown',
530 [t.path for t in self.tests_to_run])
531 # Save shutdown time
532 self.state_instance.set_shared_data('shutdown_time',
533 time.time())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800534
Jon Salz0697cbf2012-07-04 15:14:04 +0800535 with self.env.lock:
536 self.event_log.Log('shutdown', operation=test.operation)
537 shutdown_result = self.env.shutdown(test.operation)
538 if shutdown_result:
539 # That's all, folks!
540 self.run_queue.put(None)
541 return
542 else:
543 # Just pass (e.g., in the chroot).
544 test.update_state(status=TestState.PASSED)
545 self.state_instance.set_shared_data(
546 'tests_after_shutdown', None)
547 # Send event with no fields to indicate that there is no
548 # longer a pending shutdown.
549 self.event_client.post_event(Event(
550 Event.Type.PENDING_SHUTDOWN))
551 continue
Jon Salz258a40c2012-04-19 12:34:01 +0800552
Jon Salz0697cbf2012-07-04 15:14:04 +0800553 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
554 self.invocations[test] = invoc
555 if self.visible_test is None and test.has_ui:
556 self.set_visible_test(test)
557 self.check_connection_manager()
558 invoc.start()
Jon Salz5f2a0672012-05-22 17:14:06 +0800559
Jon Salz0697cbf2012-07-04 15:14:04 +0800560 def check_connection_manager(self):
561 exclusive_tests = [
562 test.path
563 for test in self.invocations
564 if test.is_exclusive(
565 factory.FactoryTest.EXCLUSIVE_OPTIONS.NETWORKING)]
566 if exclusive_tests:
567 # Make sure networking is disabled.
568 if self.network_enabled:
569 logging.info('Disabling network, as requested by %s',
570 exclusive_tests)
571 self.connection_manager.DisableNetworking()
572 self.network_enabled = False
573 else:
574 # Make sure networking is enabled.
575 if not self.network_enabled:
576 logging.info('Re-enabling network')
577 self.connection_manager.EnableNetworking()
578 self.network_enabled = True
Jon Salz5da61e62012-05-31 13:06:22 +0800579
Jon Salz0697cbf2012-07-04 15:14:04 +0800580 def run_tests(self, subtrees, untested_only=False):
581 '''
582 Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800583
Jon Salz0697cbf2012-07-04 15:14:04 +0800584 The tests are run in order unless one fails (then stops).
585 Backgroundable tests are run simultaneously; when a foreground test is
586 encountered, we wait for all active tests to finish before continuing.
Jon Salzb1b39092012-05-03 02:05:09 +0800587
Jon Salz0697cbf2012-07-04 15:14:04 +0800588 @param subtrees: Node or nodes containing tests to run (may either be
589 a single test or a list). Duplicates will be ignored.
590 '''
591 if type(subtrees) != list:
592 subtrees = [subtrees]
Jon Salz258a40c2012-04-19 12:34:01 +0800593
Jon Salz0697cbf2012-07-04 15:14:04 +0800594 # Nodes we've seen so far, to avoid duplicates.
595 seen = set()
Jon Salz94eb56f2012-06-12 18:01:12 +0800596
Jon Salz0697cbf2012-07-04 15:14:04 +0800597 self.tests_to_run = deque()
598 for subtree in subtrees:
599 for test in subtree.walk():
600 if test in seen:
601 continue
602 seen.add(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800603
Jon Salz0697cbf2012-07-04 15:14:04 +0800604 if not test.is_leaf():
605 continue
606 if (untested_only and
607 test.get_state().status != TestState.UNTESTED):
608 continue
609 self.tests_to_run.append(test)
610 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800611
Jon Salz0697cbf2012-07-04 15:14:04 +0800612 def reap_completed_tests(self):
613 '''
614 Removes completed tests from the set of active tests.
615
616 Also updates the visible test if it was reaped.
617 '''
618 for t, v in dict(self.invocations).iteritems():
619 if v.is_completed():
620 del self.invocations[t]
621
622 if (self.visible_test is None or
623 self.visible_test not in self.invocations):
624 self.set_visible_test(None)
625 # Make the first running test, if any, the visible test
626 for t in self.test_list.walk():
627 if t in self.invocations:
628 self.set_visible_test(t)
629 break
630
631 def kill_active_tests(self, abort):
632 '''
633 Kills and waits for all active tests.
634
635 @param abort: True to change state of killed tests to FAILED, False for
636 UNTESTED.
637 '''
638 self.reap_completed_tests()
639 for test, invoc in self.invocations.items():
640 factory.console.info('Killing active test %s...' % test.path)
641 invoc.abort_and_join()
642 factory.console.info('Killed %s' % test.path)
643 del self.invocations[test]
644 if not abort:
645 test.update_state(status=TestState.UNTESTED)
646 self.reap_completed_tests()
647
648 def stop(self):
649 self.kill_active_tests(False)
650 self.run_tests([])
651
652 def abort_active_tests(self):
653 self.kill_active_tests(True)
654
655 def main(self):
656 try:
657 self.init()
658 self.event_log.Log('goofy_init',
659 success=True)
660 except:
661 if self.event_log:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800662 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800663 self.event_log.Log('goofy_init',
664 success=False,
665 trace=traceback.format_exc())
666 except: # pylint: disable=W0702
667 pass
668 raise
669
670 self.run()
671
672 def update_system_info(self):
673 '''Updates system info.'''
674 system_info = system.SystemInfo()
675 self.state_instance.set_shared_data('system_info', system_info.__dict__)
676 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
677 system_info=system_info.__dict__))
678 logging.info('System info: %r', system_info.__dict__)
679
680 def update_factory(self):
681 self.kill_active_tests(False)
682 self.run_tests([])
683
684 try:
685 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
686 self.env.shutdown('reboot')
687 except: # pylint: disable=W0702
688 factory.console.exception('Unable to update')
689
690 def init(self, args=None, env=None):
691 '''Initializes Goofy.
692
693 Args:
694 args: A list of command-line arguments. Uses sys.argv if
695 args is None.
696 env: An Environment instance to use (or None to choose
697 FakeChrootEnvironment or DUTEnvironment as appropriate).
698 '''
699 parser = OptionParser()
700 parser.add_option('-v', '--verbose', dest='verbose',
701 action='store_true',
702 help='Enable debug logging')
703 parser.add_option('--print_test_list', dest='print_test_list',
704 metavar='FILE',
705 help='Read and print test list FILE, and exit')
706 parser.add_option('--restart', dest='restart',
707 action='store_true',
708 help='Clear all test state')
709 parser.add_option('--ui', dest='ui', type='choice',
710 choices=['none', 'gtk', 'chrome'],
711 default='gtk',
712 help='UI to use')
713 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
714 type='int', default=1,
715 help=('Factor by which to scale UI '
716 '(Chrome UI only)'))
717 parser.add_option('--test_list', dest='test_list',
718 metavar='FILE',
719 help='Use FILE as test list')
720 (self.options, self.args) = parser.parse_args(args)
721
722 global _inited_logging # pylint: disable=W0603
723 if not _inited_logging:
724 factory.init_logging('goofy', verbose=self.options.verbose)
725 _inited_logging = True
726 self.event_log = EventLog('goofy')
727
728 if (not suppress_chroot_warning and
729 factory.in_chroot() and
730 self.options.ui == 'gtk' and
731 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
732 # That's not going to work! Tell the user how to run
733 # this way.
734 logging.warn(GOOFY_IN_CHROOT_WARNING)
735 time.sleep(1)
736
737 if env:
738 self.env = env
739 elif factory.in_chroot():
740 self.env = test_environment.FakeChrootEnvironment()
741 logging.warn(
742 'Using chroot environment: will not actually run autotests')
743 else:
744 self.env = test_environment.DUTEnvironment()
745 self.env.goofy = self
746
747 if self.options.restart:
748 state.clear_state()
749
750 if self.options.print_test_list:
751 print (factory.read_test_list(
752 self.options.print_test_list,
753 test_classes=dict(test_steps.__dict__)).
754 __repr__(recursive=True))
755 return
756
757 if self.options.ui_scale_factor != 1 and utils.in_qemu():
758 logging.warn(
759 'In QEMU; ignoring ui_scale_factor argument')
760 self.options.ui_scale_factor = 1
761
762 logging.info('Started')
763
764 self.start_state_server()
765 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
766 self.state_instance.set_shared_data('ui_scale_factor',
767 self.options.ui_scale_factor)
768 self.last_shutdown_time = (
769 self.state_instance.get_shared_data('shutdown_time', optional=True))
770 self.state_instance.del_shared_data('shutdown_time', optional=True)
771
772 if not self.options.test_list:
773 self.options.test_list = find_test_list()
774 if not self.options.test_list:
775 logging.error('No test list. Aborting.')
776 sys.exit(1)
777 logging.info('Using test list %s', self.options.test_list)
778
779 self.test_list = factory.read_test_list(
780 self.options.test_list,
781 self.state_instance,
782 test_classes=dict(test_steps.__dict__))
783 if not self.state_instance.has_shared_data('ui_lang'):
784 self.state_instance.set_shared_data('ui_lang',
785 self.test_list.options.ui_lang)
786 self.state_instance.set_shared_data(
787 'test_list_options',
788 self.test_list.options.__dict__)
789 self.state_instance.test_list = self.test_list
790
791 self.init_states()
792 self.start_event_server()
793 self.connection_manager = self.env.create_connection_manager(
794 self.test_list.options.wlans)
795 # Note that we create a log watcher even if
796 # sync_event_log_period_secs isn't set (no background
797 # syncing), since we may use it to flush event logs as well.
798 self.log_watcher = EventLogWatcher(
799 self.test_list.options.sync_event_log_period_secs,
800 handle_event_logs_callback=self._handle_event_logs)
801 if self.test_list.options.sync_event_log_period_secs:
802 self.log_watcher.StartWatchThread()
803
804 self.update_system_info()
805
806 os.environ['CROS_FACTORY'] = '1'
807 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
808
809 # Set CROS_UI since some behaviors in ui.py depend on the
810 # particular UI in use. TODO(jsalz): Remove this (and all
811 # places it is used) when the GTK UI is removed.
812 os.environ['CROS_UI'] = self.options.ui
813
814 if self.options.ui == 'chrome':
815 self.env.launch_chrome()
816 logging.info('Waiting for a web socket connection')
817 self.web_socket_manager.wait()
818
819 # Wait for the test widget size to be set; this is done in
820 # an asynchronous RPC so there is a small chance that the
821 # web socket might be opened first.
822 for _ in range(100): # 10 s
823 try:
824 if self.state_instance.get_shared_data('test_widget_size'):
825 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800826 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800827 pass # Retry
828 time.sleep(0.1) # 100 ms
829 else:
830 logging.warn('Never received test_widget_size from UI')
831 elif self.options.ui == 'gtk':
832 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800833
Jon Salz0697cbf2012-07-04 15:14:04 +0800834 for handler in self.on_ui_startup:
835 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800836
Jon Salz0697cbf2012-07-04 15:14:04 +0800837 self.prespawner = Prespawner()
838 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800839
Jon Salz0697cbf2012-07-04 15:14:04 +0800840 def state_change_callback(test, test_state):
841 self.event_client.post_event(
842 Event(Event.Type.STATE_CHANGE,
843 path=test.path, state=test_state))
844 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800845
Jon Salz0697cbf2012-07-04 15:14:04 +0800846 try:
847 tests_after_shutdown = self.state_instance.get_shared_data(
848 'tests_after_shutdown')
849 except KeyError:
850 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800851
Jon Salz0697cbf2012-07-04 15:14:04 +0800852 if tests_after_shutdown is not None:
853 logging.info('Resuming tests after shutdown: %s',
854 tests_after_shutdown)
855 self.state_instance.set_shared_data('tests_after_shutdown', None)
856 self.tests_to_run.extend(
857 self.test_list.lookup_path(t) for t in tests_after_shutdown)
858 self.run_queue.put(self.run_next_test)
859 else:
860 if self.test_list.options.auto_run_on_start:
861 self.run_queue.put(
862 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800863
Jon Salz0697cbf2012-07-04 15:14:04 +0800864 def run(self):
865 '''Runs Goofy.'''
866 # Process events forever.
867 while self.run_once(True):
868 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800869
Jon Salz0697cbf2012-07-04 15:14:04 +0800870 def run_once(self, block=False):
871 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800872
Jon Salz0697cbf2012-07-04 15:14:04 +0800873 Args:
874 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800875
Jon Salz0697cbf2012-07-04 15:14:04 +0800876 Returns:
877 True to keep going or False to shut down.
878 '''
879 events = utils.DrainQueue(self.run_queue)
880 if not events:
881 # Nothing on the run queue.
882 self._run_queue_idle()
883 if block:
884 # Block for at least one event...
885 events.append(self.run_queue.get())
886 # ...and grab anything else that showed up at the same
887 # time.
888 events.extend(utils.DrainQueue(self.run_queue))
Jon Salz51528e12012-07-02 18:54:45 +0800889
Jon Salz0697cbf2012-07-04 15:14:04 +0800890 for event in events:
891 if not event:
892 # Shutdown request.
893 self.run_queue.task_done()
894 return False
Jon Salz51528e12012-07-02 18:54:45 +0800895
Jon Salz0697cbf2012-07-04 15:14:04 +0800896 try:
897 event()
898 except Exception as e: # pylint: disable=W0703
899 logging.error('Error in event loop: %s', e)
900 traceback.print_exc(sys.stderr)
901 self.record_exception(traceback.format_exception_only(
902 *sys.exc_info()[:2]))
903 # But keep going
904 finally:
905 self.run_queue.task_done()
906 return True
Jon Salz0405ab52012-03-16 15:26:52 +0800907
Jon Salz0697cbf2012-07-04 15:14:04 +0800908 def _run_queue_idle(self):
909 '''Invoked when the run queue has no events.'''
910 self.check_connection_manager()
Jon Salz57717ca2012-04-04 16:47:25 +0800911
Jon Salz0697cbf2012-07-04 15:14:04 +0800912 def _handle_event_logs(self, log_name, chunk):
913 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +0800914
Jon Salz0697cbf2012-07-04 15:14:04 +0800915 Attempts to upload the event logs to the shopfloor server.
916 '''
917 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
918 start_time = time.time()
919 logging.info('Syncing %s', description)
920 shopfloor_client = shopfloor.get_instance(
921 detect=True,
922 timeout=self.test_list.options.shopfloor_timeout_secs)
923 shopfloor_client.UploadEvent(log_name, chunk)
924 logging.info(
925 'Successfully synced %s in %.03f s',
926 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +0800927
Jon Salz0697cbf2012-07-04 15:14:04 +0800928 def run_tests_with_status(self, statuses_to_run, starting_at=None,
929 root=None):
930 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +0800931
Jon Salz0697cbf2012-07-04 15:14:04 +0800932 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800933
Jon Salz0697cbf2012-07-04 15:14:04 +0800934 Args:
935 starting_at: If provided, only auto-runs tests beginning with
936 this test.
937 '''
938 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800939
Jon Salz0697cbf2012-07-04 15:14:04 +0800940 if starting_at:
941 # Make sure they passed a test, not a string.
942 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +0800943
Jon Salz0697cbf2012-07-04 15:14:04 +0800944 tests_to_reset = []
945 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +0800946
Jon Salz0697cbf2012-07-04 15:14:04 +0800947 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +0800948
Jon Salz0697cbf2012-07-04 15:14:04 +0800949 for test in root.get_top_level_tests():
950 if starting_at:
951 if test == starting_at:
952 # We've found starting_at; do auto-run on all
953 # subsequent tests.
954 found_starting_at = True
955 if not found_starting_at:
956 # Don't start this guy yet
957 continue
Jon Salz0405ab52012-03-16 15:26:52 +0800958
Jon Salz0697cbf2012-07-04 15:14:04 +0800959 status = test.get_state().status
960 if status == TestState.ACTIVE or status in statuses_to_run:
961 # Reset the test (later; we will need to abort
962 # all active tests first).
963 tests_to_reset.append(test)
964 if status in statuses_to_run:
965 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800966
Jon Salz0697cbf2012-07-04 15:14:04 +0800967 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +0800968
Jon Salz0697cbf2012-07-04 15:14:04 +0800969 # Reset all statuses of the tests to run (in case any tests were active;
970 # we want them to be run again).
971 for test_to_reset in tests_to_reset:
972 for test in test_to_reset.walk():
973 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +0800974
Jon Salz0697cbf2012-07-04 15:14:04 +0800975 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +0800976
Jon Salz0697cbf2012-07-04 15:14:04 +0800977 def restart_tests(self, root=None):
978 '''Restarts all tests.'''
979 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800980
Jon Salz0697cbf2012-07-04 15:14:04 +0800981 self.abort_active_tests()
982 for test in root.walk():
983 test.update_state(status=TestState.UNTESTED)
984 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +0800985
Jon Salz0697cbf2012-07-04 15:14:04 +0800986 def auto_run(self, starting_at=None, root=None):
987 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +0800988
Jon Salz0697cbf2012-07-04 15:14:04 +0800989 Args:
990 starting_at: If provide, only auto-runs tests beginning with
991 this test.
992 '''
993 root = root or self.test_list
994 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
995 starting_at=starting_at,
996 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +0800997
Jon Salz0697cbf2012-07-04 15:14:04 +0800998 def re_run_failed(self, root=None):
999 '''Re-runs failed tests.'''
1000 root = root or self.test_list
1001 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001002
Jon Salz0697cbf2012-07-04 15:14:04 +08001003 def show_review_information(self):
1004 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001005
Jon Salz0697cbf2012-07-04 15:14:04 +08001006 The information screene is rendered by main UI program (ui.py), so in
1007 goofy we only need to kill all active tests, set them as untested, and
1008 clear remaining tests.
1009 '''
1010 self.kill_active_tests(False)
1011 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001012
Jon Salz0697cbf2012-07-04 15:14:04 +08001013 def handle_switch_test(self, event):
1014 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001015
Jon Salz0697cbf2012-07-04 15:14:04 +08001016 @param event: The SWITCH_TEST event.
1017 '''
1018 test = self.test_list.lookup_path(event.path)
1019 if not test:
1020 logging.error('Unknown test %r', event.key)
1021 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001022
Jon Salz0697cbf2012-07-04 15:14:04 +08001023 invoc = self.invocations.get(test)
1024 if invoc and test.backgroundable:
1025 # Already running: just bring to the front if it
1026 # has a UI.
1027 logging.info('Setting visible test to %s', test.path)
1028 self.event_client.post_event(
1029 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
1030 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001031
Jon Salz0697cbf2012-07-04 15:14:04 +08001032 self.abort_active_tests()
1033 for t in test.walk():
1034 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001035
Jon Salz0697cbf2012-07-04 15:14:04 +08001036 if self.test_list.options.auto_run_on_keypress:
1037 self.auto_run(starting_at=test)
1038 else:
1039 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001040
Jon Salz0697cbf2012-07-04 15:14:04 +08001041 def wait(self):
1042 '''Waits for all pending invocations.
1043
1044 Useful for testing.
1045 '''
1046 for k, v in self.invocations.iteritems():
1047 logging.info('Waiting for %s to complete...', k)
1048 v.thread.join()
1049
1050 def check_exceptions(self):
1051 '''Raises an error if any exceptions have occurred in
1052 invocation threads.'''
1053 if self.exceptions:
1054 raise RuntimeError('Exception in invocation thread: %r' %
1055 self.exceptions)
1056
1057 def record_exception(self, msg):
1058 '''Records an exception in an invocation thread.
1059
1060 An exception with the given message will be rethrown when
1061 Goofy is destroyed.'''
1062 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001063
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001064
1065if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001066 Goofy().main()