blob: cebd415f893715ed642f8cfe92661f1afa8169f6 [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
Jon Salz46b89562012-07-05 11:49:22 +0800722 # Make sure factory directories exist.
723 factory.get_log_root()
724 factory.get_state_root()
725 factory.get_test_data_root()
726
Jon Salz0697cbf2012-07-04 15:14:04 +0800727 global _inited_logging # pylint: disable=W0603
728 if not _inited_logging:
729 factory.init_logging('goofy', verbose=self.options.verbose)
730 _inited_logging = True
731 self.event_log = EventLog('goofy')
732
733 if (not suppress_chroot_warning and
734 factory.in_chroot() and
735 self.options.ui == 'gtk' and
736 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
737 # That's not going to work! Tell the user how to run
738 # this way.
739 logging.warn(GOOFY_IN_CHROOT_WARNING)
740 time.sleep(1)
741
742 if env:
743 self.env = env
744 elif factory.in_chroot():
745 self.env = test_environment.FakeChrootEnvironment()
746 logging.warn(
747 'Using chroot environment: will not actually run autotests')
748 else:
749 self.env = test_environment.DUTEnvironment()
750 self.env.goofy = self
751
752 if self.options.restart:
753 state.clear_state()
754
755 if self.options.print_test_list:
756 print (factory.read_test_list(
757 self.options.print_test_list,
758 test_classes=dict(test_steps.__dict__)).
759 __repr__(recursive=True))
760 return
761
762 if self.options.ui_scale_factor != 1 and utils.in_qemu():
763 logging.warn(
764 'In QEMU; ignoring ui_scale_factor argument')
765 self.options.ui_scale_factor = 1
766
767 logging.info('Started')
768
769 self.start_state_server()
770 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
771 self.state_instance.set_shared_data('ui_scale_factor',
772 self.options.ui_scale_factor)
773 self.last_shutdown_time = (
774 self.state_instance.get_shared_data('shutdown_time', optional=True))
775 self.state_instance.del_shared_data('shutdown_time', optional=True)
776
777 if not self.options.test_list:
778 self.options.test_list = find_test_list()
779 if not self.options.test_list:
780 logging.error('No test list. Aborting.')
781 sys.exit(1)
782 logging.info('Using test list %s', self.options.test_list)
783
784 self.test_list = factory.read_test_list(
785 self.options.test_list,
786 self.state_instance,
787 test_classes=dict(test_steps.__dict__))
788 if not self.state_instance.has_shared_data('ui_lang'):
789 self.state_instance.set_shared_data('ui_lang',
790 self.test_list.options.ui_lang)
791 self.state_instance.set_shared_data(
792 'test_list_options',
793 self.test_list.options.__dict__)
794 self.state_instance.test_list = self.test_list
795
796 self.init_states()
797 self.start_event_server()
798 self.connection_manager = self.env.create_connection_manager(
799 self.test_list.options.wlans)
800 # Note that we create a log watcher even if
801 # sync_event_log_period_secs isn't set (no background
802 # syncing), since we may use it to flush event logs as well.
803 self.log_watcher = EventLogWatcher(
804 self.test_list.options.sync_event_log_period_secs,
805 handle_event_logs_callback=self._handle_event_logs)
806 if self.test_list.options.sync_event_log_period_secs:
807 self.log_watcher.StartWatchThread()
808
809 self.update_system_info()
810
811 os.environ['CROS_FACTORY'] = '1'
812 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
813
814 # Set CROS_UI since some behaviors in ui.py depend on the
815 # particular UI in use. TODO(jsalz): Remove this (and all
816 # places it is used) when the GTK UI is removed.
817 os.environ['CROS_UI'] = self.options.ui
818
819 if self.options.ui == 'chrome':
820 self.env.launch_chrome()
821 logging.info('Waiting for a web socket connection')
822 self.web_socket_manager.wait()
823
824 # Wait for the test widget size to be set; this is done in
825 # an asynchronous RPC so there is a small chance that the
826 # web socket might be opened first.
827 for _ in range(100): # 10 s
828 try:
829 if self.state_instance.get_shared_data('test_widget_size'):
830 break
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800831 except KeyError:
Jon Salz0697cbf2012-07-04 15:14:04 +0800832 pass # Retry
833 time.sleep(0.1) # 100 ms
834 else:
835 logging.warn('Never received test_widget_size from UI')
836 elif self.options.ui == 'gtk':
837 self.start_ui()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800838
Jon Salz0697cbf2012-07-04 15:14:04 +0800839 for handler in self.on_ui_startup:
840 handler()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800841
Jon Salz0697cbf2012-07-04 15:14:04 +0800842 self.prespawner = Prespawner()
843 self.prespawner.start()
Jon Salz73e0fd02012-04-04 11:46:38 +0800844
Jon Salz0697cbf2012-07-04 15:14:04 +0800845 def state_change_callback(test, test_state):
846 self.event_client.post_event(
847 Event(Event.Type.STATE_CHANGE,
848 path=test.path, state=test_state))
849 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +0800850
Jon Salz0697cbf2012-07-04 15:14:04 +0800851 try:
852 tests_after_shutdown = self.state_instance.get_shared_data(
853 'tests_after_shutdown')
854 except KeyError:
855 tests_after_shutdown = None
Jon Salz57717ca2012-04-04 16:47:25 +0800856
Jon Salz0697cbf2012-07-04 15:14:04 +0800857 if tests_after_shutdown is not None:
858 logging.info('Resuming tests after shutdown: %s',
859 tests_after_shutdown)
860 self.state_instance.set_shared_data('tests_after_shutdown', None)
861 self.tests_to_run.extend(
862 self.test_list.lookup_path(t) for t in tests_after_shutdown)
863 self.run_queue.put(self.run_next_test)
864 else:
865 if self.test_list.options.auto_run_on_start:
866 self.run_queue.put(
867 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800868
Jon Salz0697cbf2012-07-04 15:14:04 +0800869 def run(self):
870 '''Runs Goofy.'''
871 # Process events forever.
872 while self.run_once(True):
873 pass
Jon Salz73e0fd02012-04-04 11:46:38 +0800874
Jon Salz0697cbf2012-07-04 15:14:04 +0800875 def run_once(self, block=False):
876 '''Runs all items pending in the event loop.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800877
Jon Salz0697cbf2012-07-04 15:14:04 +0800878 Args:
879 block: If true, block until at least one event is processed.
Jon Salz7c15e8b2012-06-19 17:10:37 +0800880
Jon Salz0697cbf2012-07-04 15:14:04 +0800881 Returns:
882 True to keep going or False to shut down.
883 '''
884 events = utils.DrainQueue(self.run_queue)
885 if not events:
886 # Nothing on the run queue.
887 self._run_queue_idle()
888 if block:
889 # Block for at least one event...
890 events.append(self.run_queue.get())
891 # ...and grab anything else that showed up at the same
892 # time.
893 events.extend(utils.DrainQueue(self.run_queue))
Jon Salz51528e12012-07-02 18:54:45 +0800894
Jon Salz0697cbf2012-07-04 15:14:04 +0800895 for event in events:
896 if not event:
897 # Shutdown request.
898 self.run_queue.task_done()
899 return False
Jon Salz51528e12012-07-02 18:54:45 +0800900
Jon Salz0697cbf2012-07-04 15:14:04 +0800901 try:
902 event()
903 except Exception as e: # pylint: disable=W0703
904 logging.error('Error in event loop: %s', e)
905 traceback.print_exc(sys.stderr)
906 self.record_exception(traceback.format_exception_only(
907 *sys.exc_info()[:2]))
908 # But keep going
909 finally:
910 self.run_queue.task_done()
911 return True
Jon Salz0405ab52012-03-16 15:26:52 +0800912
Jon Salz0697cbf2012-07-04 15:14:04 +0800913 def _run_queue_idle(self):
914 '''Invoked when the run queue has no events.'''
915 self.check_connection_manager()
Jon Salz57717ca2012-04-04 16:47:25 +0800916
Jon Salz0697cbf2012-07-04 15:14:04 +0800917 def _handle_event_logs(self, log_name, chunk):
918 '''Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +0800919
Jon Salz0697cbf2012-07-04 15:14:04 +0800920 Attempts to upload the event logs to the shopfloor server.
921 '''
922 description = 'event logs (%s, %d bytes)' % (log_name, len(chunk))
923 start_time = time.time()
924 logging.info('Syncing %s', description)
925 shopfloor_client = shopfloor.get_instance(
926 detect=True,
927 timeout=self.test_list.options.shopfloor_timeout_secs)
928 shopfloor_client.UploadEvent(log_name, chunk)
929 logging.info(
930 'Successfully synced %s in %.03f s',
931 description, time.time() - start_time)
Jon Salz57717ca2012-04-04 16:47:25 +0800932
Jon Salz0697cbf2012-07-04 15:14:04 +0800933 def run_tests_with_status(self, statuses_to_run, starting_at=None,
934 root=None):
935 '''Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +0800936
Jon Salz0697cbf2012-07-04 15:14:04 +0800937 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800938
Jon Salz0697cbf2012-07-04 15:14:04 +0800939 Args:
940 starting_at: If provided, only auto-runs tests beginning with
941 this test.
942 '''
943 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800944
Jon Salz0697cbf2012-07-04 15:14:04 +0800945 if starting_at:
946 # Make sure they passed a test, not a string.
947 assert isinstance(starting_at, factory.FactoryTest)
Jon Salz0405ab52012-03-16 15:26:52 +0800948
Jon Salz0697cbf2012-07-04 15:14:04 +0800949 tests_to_reset = []
950 tests_to_run = []
Jon Salz0405ab52012-03-16 15:26:52 +0800951
Jon Salz0697cbf2012-07-04 15:14:04 +0800952 found_starting_at = False
Jon Salz0405ab52012-03-16 15:26:52 +0800953
Jon Salz0697cbf2012-07-04 15:14:04 +0800954 for test in root.get_top_level_tests():
955 if starting_at:
956 if test == starting_at:
957 # We've found starting_at; do auto-run on all
958 # subsequent tests.
959 found_starting_at = True
960 if not found_starting_at:
961 # Don't start this guy yet
962 continue
Jon Salz0405ab52012-03-16 15:26:52 +0800963
Jon Salz0697cbf2012-07-04 15:14:04 +0800964 status = test.get_state().status
965 if status == TestState.ACTIVE or status in statuses_to_run:
966 # Reset the test (later; we will need to abort
967 # all active tests first).
968 tests_to_reset.append(test)
969 if status in statuses_to_run:
970 tests_to_run.append(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800971
Jon Salz0697cbf2012-07-04 15:14:04 +0800972 self.abort_active_tests()
Jon Salz258a40c2012-04-19 12:34:01 +0800973
Jon Salz0697cbf2012-07-04 15:14:04 +0800974 # Reset all statuses of the tests to run (in case any tests were active;
975 # we want them to be run again).
976 for test_to_reset in tests_to_reset:
977 for test in test_to_reset.walk():
978 test.update_state(status=TestState.UNTESTED)
Jon Salz57717ca2012-04-04 16:47:25 +0800979
Jon Salz0697cbf2012-07-04 15:14:04 +0800980 self.run_tests(tests_to_run, untested_only=True)
Jon Salz0405ab52012-03-16 15:26:52 +0800981
Jon Salz0697cbf2012-07-04 15:14:04 +0800982 def restart_tests(self, root=None):
983 '''Restarts all tests.'''
984 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800985
Jon Salz0697cbf2012-07-04 15:14:04 +0800986 self.abort_active_tests()
987 for test in root.walk():
988 test.update_state(status=TestState.UNTESTED)
989 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +0800990
Jon Salz0697cbf2012-07-04 15:14:04 +0800991 def auto_run(self, starting_at=None, root=None):
992 '''"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +0800993
Jon Salz0697cbf2012-07-04 15:14:04 +0800994 Args:
995 starting_at: If provide, only auto-runs tests beginning with
996 this test.
997 '''
998 root = root or self.test_list
999 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
1000 starting_at=starting_at,
1001 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001002
Jon Salz0697cbf2012-07-04 15:14:04 +08001003 def re_run_failed(self, root=None):
1004 '''Re-runs failed tests.'''
1005 root = root or self.test_list
1006 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz57717ca2012-04-04 16:47:25 +08001007
Jon Salz0697cbf2012-07-04 15:14:04 +08001008 def show_review_information(self):
1009 '''Event handler for showing review information screen.
Jon Salz57717ca2012-04-04 16:47:25 +08001010
Jon Salz0697cbf2012-07-04 15:14:04 +08001011 The information screene is rendered by main UI program (ui.py), so in
1012 goofy we only need to kill all active tests, set them as untested, and
1013 clear remaining tests.
1014 '''
1015 self.kill_active_tests(False)
1016 self.run_tests([])
Jon Salz57717ca2012-04-04 16:47:25 +08001017
Jon Salz0697cbf2012-07-04 15:14:04 +08001018 def handle_switch_test(self, event):
1019 '''Switches to a particular test.
Jon Salz0405ab52012-03-16 15:26:52 +08001020
Jon Salz0697cbf2012-07-04 15:14:04 +08001021 @param event: The SWITCH_TEST event.
1022 '''
1023 test = self.test_list.lookup_path(event.path)
1024 if not test:
1025 logging.error('Unknown test %r', event.key)
1026 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001027
Jon Salz0697cbf2012-07-04 15:14:04 +08001028 invoc = self.invocations.get(test)
1029 if invoc and test.backgroundable:
1030 # Already running: just bring to the front if it
1031 # has a UI.
1032 logging.info('Setting visible test to %s', test.path)
1033 self.event_client.post_event(
1034 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
1035 return
Jon Salz73e0fd02012-04-04 11:46:38 +08001036
Jon Salz0697cbf2012-07-04 15:14:04 +08001037 self.abort_active_tests()
1038 for t in test.walk():
1039 t.update_state(status=TestState.UNTESTED)
Jon Salz73e0fd02012-04-04 11:46:38 +08001040
Jon Salz0697cbf2012-07-04 15:14:04 +08001041 if self.test_list.options.auto_run_on_keypress:
1042 self.auto_run(starting_at=test)
1043 else:
1044 self.run_tests(test)
Jon Salz73e0fd02012-04-04 11:46:38 +08001045
Jon Salz0697cbf2012-07-04 15:14:04 +08001046 def wait(self):
1047 '''Waits for all pending invocations.
1048
1049 Useful for testing.
1050 '''
1051 for k, v in self.invocations.iteritems():
1052 logging.info('Waiting for %s to complete...', k)
1053 v.thread.join()
1054
1055 def check_exceptions(self):
1056 '''Raises an error if any exceptions have occurred in
1057 invocation threads.'''
1058 if self.exceptions:
1059 raise RuntimeError('Exception in invocation thread: %r' %
1060 self.exceptions)
1061
1062 def record_exception(self, msg):
1063 '''Records an exception in an invocation thread.
1064
1065 An exception with the given message will be rethrown when
1066 Goofy is destroyed.'''
1067 self.exceptions.append(msg)
Jon Salz73e0fd02012-04-04 11:46:38 +08001068
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001069
1070if __name__ == '__main__':
Jon Salz0697cbf2012-07-04 15:14:04 +08001071 Goofy().main()